Основы офисного программирования и документы Word

Офисное программирование. Специфика и примеры

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

Особенности офисного программирования

В чем же специфика офисного программирования, чем оно отличается от программирования в программной среде таких языков как VB, VC++ или Delphi, ориентированных на создание программных проектов различного типа? Выделим характерные особенности:

  • Среда разработки. Мощная и разнообразная среда приложений Office, в которой можно создавать документы разного типа и работать с ними. Поскольку эта среда ориентирована в первую очередь не на программистов, а на пользователей, то в ней можно создавать документы без всякого программирования. Поэтому программист обычно начинает не на пустом месте, он начинает работать с документами, их заготовками, созданными пользователями. Заметьте, что и сам программист может выступать в роли пользователя и сочетать в своей работе традиционное программирование с работой "вручную", без программирования. При программировании офисных документов сама среда представлена в виде объектов, свойства, методы и события которых доступны в языке программирования VBA.
  • Совместная работа. В работе над документами Office могут естественным образом сотрудничать как программисты, так и пользователи - специалисты, работающие с документом, возможно, создающие его руками, но не занимающиеся программированием. Мне кажется, что здесь может быть преодолен всегда существовавший барьер между разработчиками программы и ее пользователями. Совместная, тесная работа над документами специалиста в некоторой предметной области и программиста-профессионала характерна для офисного программирования и реально может приводить к качественным эффектам, уменьшая время разработки этих документов и улучшая их качество. При совместной работе над документами может быть существенно сокращен типичный для программных продуктов цикл разработки, включающий такие этапы, как создание прототипа системы и его бета - тестирование.
  • Цели разработки.Может быть, именно с этого пункта и следовало начинать, говоря об особенностях офисного программирования. Дело в том, что меняются цели, приоритеты, сам взгляд на сущность работы программиста, работающего в среде Office. Ранее целью программиста было создание приложения, понимаемого как программа, программный проект. Теперь программист является одним из участников (возможно единственным) создания системы документов. Документ, а не программа, становится целью разработки. Программный проект - это лишь часть документа. В Office программный проект неразрывно связан с документом, хранится, как часть документа, и не может существовать независимо от него.
  • Настройка документов. Слово "Настройка" (Custom-built) является одним из ключевых слов в офисном программировании. Действительно, стандартные возможности среды по созданию и работе с документами велики. Однако, возможность настроить стандартный документ Office, сделать его "по заказу", снабдить его новыми функциями, учитывающими специфику решаемой задачи, подогнать его "под себя" это одна из важнейших особенностей офисного программирования. Настройка может быть очень простой и состоять в том, что стандартный документ получает некоторые полезные дополнительные свойства, расширяющие его возможности или внешний вид. Обычно так начинают свой путь в офисное программирование продвинутые пользователи Office. Но настройка может быть очень сложной и документ, сделанный по заказу, может ничем не напоминать обычный стандартный документ Office.
  • Каркас документа.Для программиста сам Office - это ни что иное, как обычная совокупность библиотек классов. В самом этом факте нет ничего специфического. Без библиотек классов не обходится ни одна современная среда программирования. Такие библиотеки классов составляют каркас приложений (Framework Applications). Типичным примером является библиотека MFC в языке VC++. Работа программиста в такой среде начинается с создания каркаса своего приложения (Framework Application) на основе классов, выбираемых из библиотеки каркаса приложений. Заметьте, по написанию "каркас приложений" и "каркас приложения" различаются лишь одной буквой, но эти два понятия различны по своей сути. Также как и для других программных сред, библиотеки классов Office представляют собой каркас приложений, или, что может быть точнее с содержательной точки зрения, - каркас документов (Framework Documents). Эти библиотеки классов Office содержат каркасы офисных документов - текстовых документов, документов, основу которых составляют электронные таблицы, презентации, базы данных. И хотя библиотека Office 2000 и библиотека MFC не сравнимы между собой по ряду параметров, поскольку у них все-таки разные цели, но в определенной степени библиотека Office 2000 гораздо богаче библиотеки MFC. Она позволяет создавать документы самых разных типов, обладающих с момента рождения весьма широкими возможностями. Всякий раз, когда создается новый документ, его каркас по умолчанию составляют объекты библиотек, отобранных по умолчанию для построения этого конкретного каркаса документа. Но одно из достоинств офисного программирования состоит в том, что этот "каркас по умолчанию" можно существенно изменить, добавив в документ новые свойства. Для этого достаточно включить в состав каркаса соответствующие библиотеки из числа тех, что составляют каркас документов Office. Заметим, что в Office 2000 число таких дополнительно поставляемых библиотек, а, следовательно, и набор возможностей, существен вырос по сравнению c предыдущей версией. Расширение каркаса документа не требует от программиста никаких значительных усилий, достаточно в редакторе Visual Basic выбрать пункт меню References и в появившемся списке всех возможных библиотек, включить те, которые отвечают его индивидуальным потребностям.
  • Язык программирования + Мир объектов. Программист, занимающийся настройкой офисных документов, как и всякий программист должен владеть языком программирования и таковым для него является язык VB. С другой стороны для офисного программиста не менее важно знать или, по крайней мере, хорошо ориентироваться в мире объектов Office, число которых выходит за пределы, доступные запоминанию. Единственно, что здесь помогает, это разумно сделанная в среде система помощи - сюда входит и браузер объектов, и справочная система, и система интеллектуальной поддержки Intellisence. Так что к программисту, занимающемуся офисным программированием, предъявляются дополнительные требования; помимо языка программирования он должен изучить мир объектов среды. Более того, крайне полезно уметь работать с этими объектами вручную так, как это делают обычные пользователи Office. Два слова хочу сказать о VBA, представляющий, как уже было сказано, язык VB, встроенный в среду Office. Заметьте, фирма Microsoft сделала революционный шаг, - она не стала создавать в среде Office 2000 собственный язык программирования, как это делалось ранее в большинстве известных сред (Oracle, FoxPro, AutoCad), а встроила в среду язык, уже известный программистам. Важно и то, что язык VBA является отчуждаемым от среды и может быть встроен в различные среды. Так что язык VBA в этом отношении становится схож с естественным языком, встраиваемым во все профессиональные области знания. Конечно, было бы совсем хорошо, если бы среда позволяла работать с любым известным языком программирования, так что программисту привыкшему работать с языком С++ или привыкшему к объектам Паскаля, принятым в Delphi, не пришлось бы переучиваться и привыкать к VBA.
  • MacroRecorder. Еще одна интересная особенность офисного программирования состоит в возможности создания программного проекта или, по крайней мере, его отдельных компонент автоматически без программирования. Эта возможность основана на использовании такого характерного для офисного программирования инструмента как MacroRecorder. MacroRecorder это транслятор действий, записывающий действия пользователя при работе вручную и транслирующий их в программу на языке VBA. Пользователь, работающий "вручную" в среде Office, видит зримые образы объектов среды абзацы в документах Word, ячейки в документах Excel, таблицы в документах Access, слайды в документах Power Point, папки в документах Outlook, формы с их элементами управления во всех этих документах и многие другие объекты. Пользователь может работать с зримыми образами этих объектов, вводить текст абзаца, задавать формулу в ячейке, работать с таблицами и папками, нажимать кнопки и выбирать элементы из раскрывающихся списков. Заметьте, реальный мир Office это мир его объектов. Пользователь не знает реального мира, он работает в мире образов, но система, как внимательный наблюдатель следит за всеми действиями пользователя и в ответ на них изменяет свойства объектов, вызывает обработчики событий, реагируя на возникающие события, вызывает методы объектов Office. Поскольку все действия пользователя транслируются в действия над объектами Office, то нетрудно, включив MacroRecorder, записать нужные действия и создать макрос программу на языке VBA, описывающую действия пользователя в терминах работы с объектами. Сегодня возможности MacroRecorder ограничены, он не очень интеллектуален и не может распознать ошибочные действия пользователя и их последующие исправления, он не может транслировать действия пользователя при его работе с целым рядом объектов, например, объектами из коллекции Shapes, встраиваемыми в документы Office 2000.

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

    К сожалению, Microsoft все в меньшей степени поддерживает MacroRecorder в своих новых разработках.

  • Два крайних случая. Интересно рассмотреть два крайних случая, возникающих при работе с офисными документами. Среди всех офисных документов выделим два типа документов, которые будем условно называть документами типа "обложка" и документами типа "ручная работа". В первом случае под обложкой документа Office содержится обычный программный проект на языке VBA, не использующий, или почти не использующий обращений к объектам Office. Типичным классом таких документов могут служить игры, - карточные и другие, реализованные в Office, но никак не ориентированные на его специальные возможности. Другая причина в создании таких документов - обложек может быть еще проще. Программист привык к этой удобной среде и создает нетипичный для среды документ, используя привычный для него язык VBA. Документы типа "ручная работа" распространены в гораздо большей степени. Такими являются все документы, создаваемые пользователями Office, не использующими VBA. Большинство начинающих да и давно работающих пользователей вполне удовлетворены стандартным набором возможностей и могут даже не подозревать о тех скрытых возможностях, которые открывает для них офисное программирование. Заметьте, что подобные документы могут быть частью "серьезных проектов", когда средствами офисного программирования создается система документов для решения сложных прикладных задач. В этом и состоит мощь среды Office, что решение многих задач, возникающих в процессе деятельности организации, не требует специального программирования и может быть получено "вручную". Самым простым и типичным примером является работа с различными бланками при автоматизации деятельности офиса. Первым шагом в создании документа - бланка является его построение. Понятно, что работу по построению бланка в электронном формате проще всего и удобнее всего осуществить "вручную" в том же Excel или Word. Такие документы являются несомненной частью общей системы документов. Говоря о таких документах следует добавить, что офисный программист, чаще всего, включит MacroRecorder, фиксируя процесс создания подобного документа. Более того, он разобьет весь процесс на сравнительно небольшие шаги. Полученные макросы затем могут быть оптимизированы, унифицированы и использованы при построении подобных документов уже программным путем, избавляя квалифицированного пользователя от повторения работы, которую можно считать рутинной, коль скоро есть соответствующий макрос, решающий эту задачу. Более того, в инструментарии программиста, работающего в этой области, может быть программа, позволяющая собирать новый бланк из уже имеющихся заготовок, примерно так, как собирается "puzzle".
  • Office 2000 - платформа разработчика. Спектр применения офисного программирования широк - от настройки отдельных документов до создания серьезных решений масштаба предприятия. Благодаря интеграции с семейством серверных продуктов Microsoft, интеграции с продуктами третьих фирм, целью разработки становится создание корпоративных приложений, нацеленных на совместную работу в Internet.
  • Подведем теперь некоторые итоги и постараемся ответить на два главных вопроса:

  • Что дает офисное программирование пользователям?
  • Что дает офисное программирование программистам?
  • Пользователям и программистам

    Офисное программирование пользователям

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

    "Работать с документами Office 2000, не используя VBA, все равно, что играть на ненастроенном пианино. Играть, конечно, можно, но профессионал этого позволить себе не может".

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

    Однако, мое сравнение, как и всякое сравнение "хромает". Его можно интерпретировать и по-другому: "Фирма Microsoft предлагает своим пользователям ненастроенные документы ("расстроенное пианино"). Чтобы с ними работать, нужно их предварительно настраивать". Дабы избежать подобных заключений, несправедливых по отношению к этому прекрасному продукту, я теперь предпочитаю в подобных ситуациях говорить так:

    "Работать с документами Office 2000 все равно, что играть на хорошо настроенном пианино. Но у этого инструмента есть возможности, открывающиеся для профессионалов. Нажмите ALT + F11 и создайте собственную композицию".

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

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

    А есть ли преимущества у программиста, работающего в Office? Есть, и их, пожалуй, не меньше, чем у пользователя:

  • В его распоряжении мощная интегрированная среда. Для программиста эта среда представлена в виде совокупности хорошо организованных объектов, доступных в языке программирования и по принципу работы ничем не отличающихся от встроенных объектов языка или объектов, создаваемых самим программистом.
  • Большинство повседневных задач становятся для него простыми, - чтобы их решить, зачастую достаточно стандартных средств.
  • Там, где стандартных средств не хватает, где у документа должны появится новые функциональные возможности, где необходимо создать документ по заказу вступает в силу язык программирования - VBA, существенная особенность которого - возможность работы с объектами любого из приложений Office.
  • Office 2000- это среда разработки, отвечающая современному принципу: "Простые задачи должны решаться просто". Мощность среды определяется тем, какие задачи для нее являются простыми. В этом отношении Office 2000 уникален - круг "простых" для него задач весьма широк. Задачи, традиционно считающиеся сложными, программист может успешно решить в среде Office 2000.
  • Если говорить о сложных задачах, то, естественно, есть такие задачи, для которых стандартных средств Office 2000 и языка VBA недостаточно. Вообще следует приветствовать одновременное существование различных программных сред, операционных систем, любимых теми или иными программистами. Лучшая позиция программиста должна состоять не в противопоставлении программных продуктов различных производителей, а в их совместном использовании. Именно поэтому мне кажутся важными идеи компонентного программирования, где вырабатывается стандарт на взаимодействие компонент, создаваемых в разных программных средах, на разных языках, на разных платформах и находящихся на разных машинах. Компонентный подход одна из характерных особенностей офисного программирования. Работа с компонентами DLL, ActiveX, AddIns, ComAddIns все это неотъемлемая часть арсенала офисного программирования.
  • Офисное программирование это развивающееся направление в программировании, так что у программистов есть все возможности внести свой вклад в создание технологий и приемов работы с офисными документами. Одним из наиболее перспективных таких направлений, развиваемых в Office 2000, несомненно, является работа с документами, опубликованными в Internet и Intranet. Web-страницы становятся тем рабочим пространством, где члены рабочей группы совместно работают над документами Office 2000.
  • Заключая этот параграф, хочется еще раз напомнить пользователям и программистам, что именно их совместная работа над офисными документами может дать наилучшие результаты. В этом состоит одна из главных особенностей офисного программирования.

    Реализация известной игры или создание документа - обложки.

    Давайте перейдем от лозунгов и призывов к конкретным делам. Для начала рассмотрим создание документа - обложки. Как мы говорили ранее, это некоторый специальный вид офисных документов, в которых не требуется знания объектов Office 2000. Такой документ представляет программный проект под обложкой одного из офисных документов, причем не важно какую из обложек выбрать - Word, Excel или, например, Power Point. Создается такой документ почти также как и программный проект в любой визуальной среде. По этой причине он достаточно легко переносится в любую из этих сред. Типичным примером таких документов, как мы говорили, являются игры. Рассмотрим посему реализацию хорошо известной игры "Волк, Коза и Капуста". Для понимания реализации нужно знать основы VBA и работу с интерфейсными объектами формами и их элементами.

    Игра "Волк, Коза и Капуста"

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

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

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

    В нашей игре есть 8 основных объектов: Человек, Волк, Коза, Капуста, Река, Левый Берег, Правый Берег, Лодка. Я добавил еще один объект - Акулу, чтобы реку было страшнее переплывать. Эти объекты будут иметь видимые образы, манипулируя которыми игрок добивается достижения поставленной цели, доставить человека, козу, волка и капусту с левого на правый берег. Полем, где будет разворачиваться действие игры, будет обычная форма VBA, она будет образом объекта Река. На реке (форме) я размещу образы других объектов игры. Каждый из этих объектов будет задаваться объектом VBA класса Image из библиотеки классов MSForms.

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

    Перед программистом, реализующим эту игру, стоят две основные задачи:

  • Спроектировать визуальный интерфейс игры, форму с ее объектами.
  • Написать обработчики событий для интерфейсных объектов.
  • Первую из этих задач он, скорее всего, как это сделал я, будет решать руками, визуально проектируя интерфейс, для решения второй задачи ему потребуется знание языка VBA и интерфейсных объектов. Знание объектов Office 2000 (объектов Word, Excel, Power Point) ему не понадобится, под какой бы документной обложкой не был реализован этот программный проект.

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

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

    (рис 6.1) Форма в начальном состоянии в игре "Волк, Коза и Капуста"

    Проектирование интерфейса

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

  • Создание формы - образа объекта Река. Итак, я начал с того, что нажал ALT +F11 и попал в среду Редактора VBA. В меню Insert я выбрал пункт UserForm, получил заготовку формы и приступил к заданию ее свойств. Первым делом я подобрал подходящие размеры формы. Это можно делать руками, но, в тех случаях, когда желательно задать точные, целочисленные значения, можно задать значения свойств Height и Width. Я изменил имя формы, назвав ее WGCForm, и дал заголовок "Wolf, Goat and Cabbage". Главное, я изменил свойство BackColor, выбрав из палитры цвет, подходящий для речной глади. Теперь форма стала похожей на реку. Еще одно серьезное изменение свойств состояло в том, что форму я сделал немодальной, изменив свойство ShowModal. Форма VBA является, как известно, обычным диалоговым окном. Всякое такое окно может быть модальным или немодальным. Из модальных окон нельзя выйти, не закончив диалога. Если же форму сделать немодальной, то это важное свойство формы позволит выходить из нее, не завершив диалога, зани маться другими делами, а потом возвращаться в форму. В частности, это позволяет во время игры при необходимости читать документ Word или создавать его, как это делаю я, имея форму перед глазами.
  • Создание основных объектов и их размещение. На следующем шаге я разместил на форме 8 одинаковых элементов управления объектов класса Image из стандартной панели инструментов ToolBox. Осталось придать им подходящий внешний вид:
  • Два объекта представляют два берега реки. Я назвал их, задав свойство Name, соответственно LeftBank и RightBank и разместил по краям реки. Никакие картинки с этими объектами можно не связывать, достаточно выбрать подходящий цвет фона и размеры.
  • У левого берега реки я поместил объект, который получил имя Boat, и связал с ним соответствующую картинку лодки. Еще один объект Shark с картинкой акулы я разместил в реке. Он введен больше для антуража и как будущий возможный герой при расширении игры. Вместе с тем он будет играть и некоторую полезную роль, позволяя в любой момент начать игру с начала.
  • Четыре объекта размещены на левом берегу реки это наши герои: человек, волк коза и капуста. Конечно, для этих элементов необходимо задать свойство Picture так, чтобы соответствующая картинка соответствовала образу объекта. Благо с этим теперь особых проблем нет, так как галерея ClipArt, поставляемая с Office 2000, содержит тысячи различных картинок. Проблема состоит лишь в том, чтобы из этого изобилия выбрать подходящую картинку. Напомню, что поиск в галерее ClipArt возможен по ключевому слову. Задайте, например, "goats" для поиска картинки с изображением козы, учитывая, что при задании ключевого слова лучше использовать множественное число. Итак, я нашел картинки, подогнал их под выбранные размеры Image-элементов, установив подходящее значение свойства PictureSizeMode. Мои объекты получили соответствующие имена: Man, Wolf, Goat and Cabbage.
  • Обратите внимание, порядок размещения Image-элементов имеет значение, поскольку при их частичном совмещении один из них (более поздний) перекрывает другой. Поскольку такая ситуация будет иметь место, когда герои игры собираются в лодке, то целесообразно вначале на форме разместить лодку, а изображение человека разместить последним. Результат моих действий показан на предыдущем рисунке. При некотором навыке, когда картинки под рукой, все действие по формированию интерфейса занимает 10 -15 минут. У меня на это ушло больше времени, так как из-за отсутствия способностей к рисованию проектирование интерфейса это не самая сильная моя сторона. И здесь как раз тот самый случай, когда такие задачи должен решать специалист - дизайнер, а не программист.

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

    Программирование игры

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

  • Введение программных объектов и их инициализация.
  • Посадка пассажиров в лодку.
  • Высадка на берег
  • Переправа через реку.
  • Тестирование ситуаций.
  • Организация диалогов
  • Будем следовать этой схеме при описании реализации игры.

    Программные объекты и их инициализация

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

    Для реализации игры достаточно ввести 5 переменных, следящих за состоянием перемещаемых объектов: человека, волка, козы, капусты и лодки. Каждый из них может находиться в трех состояниях, быть на левом берегу, правом берегу или в лодке (у объекта Boat всего два состояния). Еще одна переменная будет следить за числом пассажиров в лодке. Вот описания из раздела Declarations стандартного модуля с именем WGCModule, который я создал первым делом:

    Option Explicit
    'Состояния наших объектов - человека, волка, козы, капусты и лодки
    Public StateOfMan As String
    Public StateOfWolf As String
    Public StateOfGoat As String
    Public StateOfCabbage As String
    Public StateOfBoat As String
    Public CountInBoat As Byte	'Число пассажиров в лодке
    Public WidthOfRiver	'Ширина реки, используемая при переправе

    Приведем теперь процедуру InitialStates, которая задает начальные установки для наших переменных и визуальных объектов:

    Public Sub InitialStates()
    	'Задаем начальные состояния объектов и их образов
    	StateOfMan = "LeftBank"
    	StateOfWolf = "LeftBank"
    	StateOfGoat = "LeftBank"
    	StateOfCabbage = "LeftBank"
    	StateOfBoat = "LeftBank"
    	CountInBoat = 0
    	
    	With WGCForm
    		.Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10
    		.Man.Visible = True
    		.Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10
    		.Wolf.Visible = True
    		.Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10
    		.Goat.Visible = True
    		.Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10
    		.Cabbage.Visible = True
    		
    		.Boat.Top = .LeftBank.Top + 130: .Boat.Left = .LeftBank.Left + _ .LeftBank.Width
    		.Boat.Visible = True
    		WidthOfRiver = .Width - .LeftBank.Width - .RightBank.Width _
    			- .Boat.Width
    	End With
    	
    
    End Sub

    Процедура InitialStates будет вызываться в ответ на такие события, как инициализация или активация нашей формы:

    Private Sub UserForm_Activate()
    	InitialStates
    End Sub
    
    Private Sub UserForm_Initialize()
    	InitialStates
    End Sub

    Как посадить пассажира в лодку одним щелчком

    Рассмотрим вначале самый простой способ посадки пассажира в лодку. Щелчок левой кнопки мыши на видимом объекте образе одного из наших героев будет перемещать его (образ) в лодку. При этом, естественно, для того, чтобы перемещение было допустимым, нужно чтобы герой и лодка находились на одном берегу. Программно перемещение объекта в лодку делается чрезвычайно просто, для этого достаточно изменить свойства Top и Left, дав им новые значения, зависящие от значения этих свойств объекта Boat. Приведем соответствующие обработчики события Click для каждого из героев игры:

    Private Sub Man_Click()
    	ManInBoat
    	'Call IntoBoat(Me.Man, StateOfMan)
    	
    End Sub
    
    Private Sub Wolf_Click()
    	WolfInBoat
    	'Call IntoBoat(Me.Wolf, StateOfWolf)
    End Sub
    
    Private Sub Goat_Click()
    	GoatInBoat
    	'Call IntoBoat(Me.Goat, StateOfGoat)
    	
    End Sub
    
    Private Sub Cabbage_Click()
    	CabbageInBoat
    	'Call IntoBoat(Me.Cabbage, StateOfCabbage)
    	
    End Sub

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

    Public Sub ManInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfMan = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfMan = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Man.Top = .Boat.Top - 30
    			.Man.Left = .Boat.Left + 25
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub
    
    Public Sub WolfInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfWolf = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfWolf = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Wolf.Top = .Boat.Top - 5
    			.Wolf.Left = .Boat.Left + 50
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub
    
    Public Sub GoatInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfGoat = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfGoat = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Goat.Top = .Boat.Top - 20
    			.Goat.Left = .Boat.Left + 100
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub
    
    Public Sub CabbageInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfCabbage = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfCabbage = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Cabbage.Top = .Boat.Top + 5
    			.Cabbage.Left = .Boat.Left + 5
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub

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

    Public Sub IntoBoat(Im As Image, ByRef St As String)
    
     'Посадка пассажиров в лодку
    	If St = StateOfBoat Then 'лодка и пассажир на одном берегу
    		St = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			Im.Left = .Boat.Left + 5 + CountInBoat * 50
    			Im.Top = .Boat.Top - CountInBoat * 30
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub

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

    Как посадить пассажира в лодку простым перетаскиванием

    Для ребенка, играющего в эту игру, вряд ли на первых порах будет понятно, почему щелчок по объекту приводит к его перемещению в лодку. Интуитивно, более разумным действием, приводящим к посадке объекта в лодку, было бы прямое перетаскивание объекта в лодку из его текущего положения на берегу. Реализацией такого способа перемещения объекта мы сейчас и займемся. При этом мы не будем тащить сам объект, хотя и это не трудно было бы сделать, а используем технику, основанную на специальном объекте DataObject, входящем в состав библиотеки MSForms. Эта техника хороша тем, что она позволяет опустить перетаскиваемый объект точно в лодку. Всякая попытка опустить объект где-либо в другом месте приведет к неуспеху операции перетаскивания. Момент, когда цель перетаскивания достигнута, и можно отпустить нажатую до этого кнопку мыши, определяется тем, что изменяется внешний вид курсора (появляется значок "+"), что и позволяет точно опустить объект в нужное место. Реализацию этого довольно сложного сп особа перетаскивания обеспечивают обработчики трех событий. Первое из этих событий связано с самим перетаскиваемым объектом. В тот момент, когда над объектом нажимается левая кнопка мыши с целью начать его перетаскивать или копировать в точку назначения, возникает событие "MouseMove". В обработчике этого события и следует создать новый объект класса DataObject, определить некоторые его свойства и вызвать метод StartDrag. Этот метод работает совсем не так, как большинство обычных методов. Его действие оканчивается в тот момент, когда завершится операция перетаскивания. В качестве результата метод возвращает 0, если операция закончилась неуспехом, и ненулевое значение в противном случае. Заметьте, что во время перетаскивания, то есть еще до того, как StartDrag завершит работу, будут возникать другие события и, следовательно, будут работать другие обработчики событий. Два таких события будут возникать, когда перетаскиваемый объект достигает точки назначения, точнее, области назначения. В этот момент у целевого объекта возникает событие BeforeDragOver. Обработчик этого события изменяет внешний вид курсора, что является сигналом достижения цели назначения и позволяет отпустить нажатую кнопку мыши. Обработчик второго события у целевого объекта BeforeDropOrPaste и реализует операцию опускания объекта. Если все завершится благополучно, то успехом заканчивает свою работу и метод StartDrag, которому возвращается управление. Подробнее обо всех деталях работы с этим объектом можно прочитать в моей книге, ссылка на соответствующее место в которой была уже сделана. После всех этих пояснений можно привести и обработчики соответствующих событий, реализующих операции по перетаскиванию объектов в лодку:

    Private Sub Man_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    	
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Man"
    		Effect = MyDataObject.StartDrag
    		If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes8 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub
    Private Sub Wolf_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Wolf"
    		Effect = MyDataObject.StartDrag
    	If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes9 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub
    Private Sub Goat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Goat"
    		Effect = MyDataObject.StartDrag
    		If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes10 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub
    Private Sub Cabbage_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Cabbage"
    		Effect = MyDataObject.StartDrag
    		If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes11 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub

    Заметьте, для четырех перемещаемых объектов: человека, волка, козы и капусты написаны четыре обработчика события MouseMove. Каждый из них создает свой объект DataObject, запоминает в его свойстве Text название перемещаемого объекта и запускает метод StartDrag. Но цель у всех этих объектов одна - лодка. У объекта Boat два обработчика событий, вне зависимости от числа пассажиров лодки. Обработчик события BeforeDragOver для всех перемещаемых объектов один и тот же, поскольку его задача изменить вид курсора при попадании перемещаемого объекта в область назначения. Обработчик события BeforeDropOrPaste более сложный. Он должен произвести разбор случаев и определить, какой именно объект прибыл в точку назначения и соответствующим образом расположить его в лодке. Анализ свойства Text, объекта DataObject, переданного в качестве параметра обработчику событий, позволяет провести разбор случаев. После этих предварительных замечаний приведем тексты самих обработчиков:

    Private Sub Boat_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора.
    	Cancel = True
    	Effect = fmDropEffectCopy
    End Sub
    
    Private Sub Boat_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения.
    	Cancel = True
    	Effect = fmDropEffectCopy
    	'Разбор случае. Анализ прибывшего объекта и вызов процедуры его размещения в лодке
    	If Data.GetText = "Man" Then
    		ManInBoat
    	ElseIf Data.GetText = "Wolf" Then
    		WolfInBoat
    	ElseIf Data.GetText = "Goat" Then
    		GoatInBoat
    	Else: CabbageInBoat
    	End If
    End Sub

    На этом закончим рассмотрение задачи посадки пассажиров в лодку и перейдем к рассмотрению следующей задачи.

    Переправа. Берег Левый - Берег Правый

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

    Private Sub Boat_Click()
    	'Переправа на другой берег
    	Crossing
    End Sub
    
    Public Sub Crossing()
    	'Переправа на другой берег
    	'Есть ли человек в лодке
    	With WGCForm
    	 
    		If StateOfMan = "InBoat" Then
    			If StateOfBoat = "LeftBank" Then
    				'Едем на правый берег
    				'Меняем координаты лодки и кормчего
    				StateOfBoat = "RightBank"
    				.Boat.Left = .Boat.Left + WidthOfRiver
    				.Man.Left = .Man.Left + WidthOfRiver
    				'Анализ присутствующих пассажиров
    				If StateOfWolf = "InBoat" Then
    					.Wolf.Left = .Wolf.Left + WidthOfRiver
    				End If
    				If StateOfGoat = "InBoat" Then
    					.Goat.Left = .Goat.Left + WidthOfRiver
    				End If
    				If StateOfCabbage = "InBoat" Then
    					.Cabbage.Left = .Cabbage.Left + WidthOfRiver
    				End If
    			Else
    				'Едем на левый берег
    				 'Меняем координаты лодки и кормчего
    				 StateOfBoat = "LeftBank"
    				.Boat.Left = .Boat.Left - WidthOfRiver
    				.Man.Left = .Man.Left - WidthOfRiver
    				'Анализ присутствующих пассажиров
    				If StateOfWolf = "InBoat" Then
    					.Wolf.Left = .Wolf.Left - WidthOfRiver
    				End If
    				If StateOfGoat = "InBoat" Then
    					.Goat.Left = .Goat.Left - WidthOfRiver
    				End If
    				If StateOfCabbage = "InBoat" Then
    					.Cabbage.Left = .Cabbage.Left - WidthOfRiver
    				End If
    			End If
    		End If
    		
    	End With
    End Sub

    Более интересна техника перетаскивания лодки с берега на берега. Ранее лодка служила целью назначения перетаскиваемых в нее объектов. Теперь она сама станет перемещаемым объектом и, следовательно, для нее мы создадим обработчик события MouseMove, в котором и вызовем метод StartDrag объекта DataObject. В роли целевых будут выступать в зависимости от ситуации объекты LeftBank и RightBank, для каждого из которых будут написаны по два обработчика событий BeforeDragOver и BeforeDropOrPaste. О них позже пойдет более подробный разговор, а сейчас для лодки приведем текст обработчика события MouseMove, запускающего процесс перемещения:

    Private Sub Boat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
     Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Boat" 'Запоминаем имя перемещаемого объекта
    		Effect = MyDataObject.StartDrag 'запускаем процесс перемещения
    		If Effect = 0 Then перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes15 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub

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

    Высадка пассажиров из лодки на берег

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

    Private Sub LeftBank_Click()
    	'Все пассажиры из лодки высаживаются на левый берег
    	ONLeftBank ("All")
    End Sub
    Private Sub RightBank_Click()
    	'Все пассажиры из лодки высаживаются на правый берег
    	OnRightBank ("All")
    End Sub

    Сами обработчики являются простыми, поскольку вся обработка спрятана в вызываемых процедурах стандартного модуля, которым в качестве параметра передается слово "All", указывающее на необходимость высадки на берег всех пассажиров. Эта же процедура будет использоваться и во второй стратегии, когда пассажиры высаживаются по одному. Давайте перейдем к ее рассмотрению. Для самих перетаскиваемых объектов обработчики события MouseMove, запускающие перетаскивание объекта, уже написаны. Мы рассматривали их, когда речь шла о перемещении объектов с берега в лодку. Теперь изменилась цель перетаскивания из лодки на берег, левый или правый в зависимости от того, куда причалила лодка. Поэтому нам надо лишь рассмотреть обработчики событий для целевых объектов LeftBank и RightBank. Напомним, эти объекты являются целевыми и при причаливании лодки к берегу. Вот тексты этих обработчиков:

    Private Sub LeftBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора.
    	Cancel = True
    	Effect = fmDropEffectCopy
    End Sub
    
    Private Sub LeftBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения.
    	Cancel = True
    	Effect = fmDropEffectCopy
    	'Пассажир высаживается на левый берег или лодка причаливает к нему
    	ONLeftBank (Data.GetText)
    End Sub
    Private Sub RightBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора.
    	Cancel = True
    	Effect = fmDropEffectCopy
    
    End Sub
    
    Private Sub RightBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения.
    Cancel = True
    	Effect = fmDropEffectCopy
    	'Пассажир высаживается на правый берег или лодка причаливает к нему
    	OnRightBank (Data.GetText)
    End Sub

    Все программистские сложности спрятаны в процедурах стандартного модуля OnLeftBank и OnRightBank. Именно в них предусмотрен разбор случаев, то ли лодка причалила к берегу, то ли человек, то ли какой-либо из его спутников высаживается на берег. Здесь же предусмотрена проверка того, на какой берег в данный момент возможна высадка или причаливание.

    Public Sub ONLeftBank(Who As String)
    	If StateOfBoat = "LeftBank" Then
    	'пассажиров из лодки можно высадить на левый берег
    	With WGCForm
    	If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then
    		'можно высадить человека
    		.Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10
    		StateOfMan = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then
    		'можно высадить волка
    		.Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10
    		StateOfWolf = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then
    		'можно высадить козу
    		.Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10
    		StateOfGoat = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then
    		'можно высадить капусту
    		.Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10
    		StateOfCabbage = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	End With
    	ElseIf (Who = "Boat") Then
    		'Лодка переезжает на левый берег
    		Crossing
    	End If
    	TestingState
    End Sub
    
    Public Sub OnRightBank(Who As String)
    	If StateOfBoat = "RightBank" Then
    	'пассажиров из лодки можно высадить на правый берег
    	With WGCForm
    	If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then
    		'можно высадить человека
    		.Man.Top = .RightBank.Top + 15: .Man.Left = .RightBank.Left + 10
    		StateOfMan = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then
    		'можно высадить волка
    		.Wolf.Top = .RightBank.Top + 80: .Wolf.Left = .RightBank.Left + 10
    		StateOfWolf = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then
    		'можно высадить козу
    		.Goat.Top = .RightBank.Top + 140: .Goat.Left = .RightBank.Left + 10
    		StateOfGoat = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then
    		'можно высадить капусту
    		.Cabbage.Top = .RightBank.Top + 200: .Cabbage.Left = .RightBank.Left + 10
    		StateOfCabbage = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	End With
    	ElseIf (Who = "Boat") Then
    	'Лодка переезжает на правый берег
    	Crossing
    	End If
    	TestingState
    End Sub

    Тестирование состояний и организация диалога

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

    Public Sub TestingState()
    	'Тестирование состояний
    	With WGCForm
    	'Волк съел козу
    	If (StateOfWolf = StateOfGoat) And (StateOfWolf <> StateOfMan) Then
    		.Goat.Visible = False
    		MsgBox Prompt:=Mes1 + vbCrLf + Mes3, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		InitialStates
    	End If
    	'Коза съела капусту
    	If (StateOfCabbage = StateOfGoat) And (StateOfCabbage <> StateOfMan) Then
    		.Cabbage.Visible = False
    		MsgBox Prompt:=Mes1 + vbCrLf + Mes4, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		InitialStates
    	End If
    	'Слишком много пассажиров!
    	If (CountInBoat > 2) Then
    		If StateOfMan = "InBoat" Then .Man.Visible = False
    		If StateOfWolf = "InBoat" Then .Wolf.Visible = False
    		If StateOfGoat = "InBoat" Then .Goat.Visible = False
    		If StateOfCabbage = "InBoat" Then .Cabbage.Visible = False
    		MsgBox Prompt:=Mes1 + vbCrLf + Mes5, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		InitialStates
    	End If
    	'Конец игры - Все пассажиры собрались на правом берегу
    	If (StateOfMan = "RightBank") And (StateOfWolf = "RightBank") And _
    		(StateOfGoat = "RightBank") And (StateOfCabbage = "RightBank") Then
    		MsgBox Prompt:=Mes2 + vbCrLf + Mes6, Buttons:=vbExclamation + vbOKOnly, Title:=Mes14
    	End If
    	End With
    End Sub

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

    (рис 6.2) Одна из критических ситуаций в игре "Волк, Коза и Капуста"

    Заметим, что после нажатия кнопки "OK" в окне выдачи сообщения вызывается процедура InitialStates и игра возвращается в начальное состояние. Скажем еще несколько слов об организации диалога. Все константы, используемые в диалоге, собраны в разделе объявлений стандартного модуля WGCModule:

    'Константы для организации диалогов
    Public Const Mes1 = "Ужасно, нелепо и грустно! "
    Public Const Mes2 = "Прекрасно, как славно, отлично! "
    Public Const Mes3 = " Волк съел козу! "
    Public Const Mes4 = " Коза съела капусту! "
    Public Const Mes5 = " Лодка перевернулась, и все утонули! "
    Public Const Mes6 = " Вам это удалось! Все переправились!"
    Public Const Mes7 = " Не удалось дотащить "
    Public Const Mes8 = " Человека! "
    Public Const Mes9 = " Волка! "
    Public Const Mes10 = " Козу! "
    Public Const Mes11 = " Капусту! "
    Public Const Mes12 = " Повторите попытку! "
    Public Const Mes13 = " Беда!"
    Public Const Mes14 = " Радость! "
    Public Const Mes15 = " Лодку! "

    Такой подход позволяет при необходимости легко перейти на другой язык диалога, или сделать выбор языка параметром игры. Разумно также, но я не стал этого делать, собрать все диалоги в специальной процедуре Dialogs. Я отказался также от использования объекта Assistant при ведении диалога, хотя он очень подходит для подобных игр. Последнее решение обусловлено тем, что мне хотелось в чистом виде сохранить концепцию документа - обложки, а объект Assistant является специфическим объектом Office 2000. Его использование не позволило бы перенести игру без всяких хлопот в чистый VB.

    Завершающий шаг

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

    (рис 6.3) Окончание игры "Волк, Коза и Капуста"

    Чтобы начать игру с начала, достаточно щелкнуть по объекту Shark, который до сих пор никак еще не использовался. В ответ на событие Click вызывается процедура InitialStates:

    Private Sub Shark_Click()
    	InitialStates
    End Sub

    Для того чтобы игра начиналась с открытием документа - обложки, зададим обработку события Open для этого документа:

    Private Sub Document_Open()
    	WGCForm.Show
    End Sub

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

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

    Перенос игры в чистый VB

    Я уже говорил, что одно из достоинств документов типа "обложка", что они сравнительно легко переносятся в другие программные среды. Проще всего, программный проект, сделанный на VBA и находящийся под обложкой одного из документов Office 2000, перенести в чистый VB. Для этого не потребуется почти никаких усилий. Этим мы сейчас и займемся. Наш программный проект состоит из одной формы WGCForm и одного стандартного модуля WGCModule. Нет никаких проблем, чтобы экспортировать эту форму и модуль, сохранив их в виде файлов с уточнениями frm и bas соответственно. Для этого достаточно выбрать пункт ExportFile из главного меню File или из контекстного меню, доступного при нажатии правой кнопки на модуле или форме в окне проекта. После того, как форма и модуль сохранены, они могут быть импортированы в любой из документов Office 2000, где начнут работать под другой обложкой. Более интересно, что точно также они могут быть импортированы в чистый VB, что я и сделал. Взгляните, как выглядит проект VB после переноса в него нашей формы и модуля.

    (рис 6.4) Проект WGC, перенесенный в среду VB6

    Следует обратить внимание на то, что формы VB и VBA это все-таки разные формы для VB. Формы VBA загружаются в среде VB специальным загрузчиком таких форм ActiveX Designer MSForms. Этот дизайнер включен во все версии VB6, поэтому на компьютере достаточно иметь VB и нет необходимости в существовании Office 2000. Подобно всем другим дизайнерам VB он имеет собственную динамически подключаемую библиотеку (run time dll). Это приводит к увеличению накладных расходов, но сам перенос осуществляется без проблем. Теперь, имея проект на VB, можно использовать все преимущества этой среды, сохранив наш проект, например, в виде исполняемого exe - файла, в виде DLL или в виде ActiveX Control.

    Следует сделать одно важное замечание, связанное с переносом в VB. Мне все-таки пришлось внести одно изменение в программный текст, прежде, чем проект заработал. Это изменение связано с объектом DataObject. Дело в том, что в VBA - проекте я использовал этот объект, не уточнив его полное имя. По умолчанию предполагалось, что речь идет об объекте DataObject библиотеки MSForms. Когда же проект стал компилироваться в чистом VB, то по умолчанию этот объект стал восприниматься, как объект VB и здесь возникла некоторая путаница. Хотя язык VB позволяет работать с визуальными объектами библиотеки MSForms, он имеет и собственную, весьма похожую библиотеку визуальных объектов. Замечу, что объект DataObject существует в этой библиотеке и, выполняя практически те же функции, является более мощным объектом. Вместо методов GetText и SetText он обладает методами GetData и SetData, способными передавать не только текст, но и данные более сложной структуры, в том числе метафайлы и графические файлы с уточнением bmp. Конеч но, я не стал переписывать текст, работая с новым, более мощным объектом VB. Я всюду в тексте заменил DataObject на полное имя MSForms.DataObject. После чего все заработало, как нужно.

    Несколько слов об обратном переносе программных проектов из VB в VBA. Такой перенос возможен, хотя выполняется сложнее. Прежде всего, замечу, что формы VB не переносятся напрямую в VBA. Для того, чтобы из VB перенести форму, необходимо разрабатывать ее, используя ActiveX Designer MSForms. Ну и, конечно, нужно следить за тем, чтобы реализация не использовала такие объекты VB, для которых нет точных аналогов в VBA, например, не следует в реализации использовать возможности объекта DataObject, принадлежащего чистому VB.

    Создание документов "вручную". Программирование без программирования

    Документы типа "ручная работа"

    Я уже говорил о том, что офисному программисту приходится начинать свою работу над документом не с нуля. Зачастую, документ в значительной своей части создан вручную и обладает широким спектром стандартных возможностей. Задача программиста состоит в том, чтобы придать документу новые возможности, учитывающие специфику решаемых задач. Документы, созданные вручную и не требующие программирования или требующие его лишь в малой степени, я и называю документами типа "ручная работа". Хотя с позиций офисного программирования такие документы представляют крайний, предельный случай и менее интересны, чем "настоящие", "сшитые на заказ" документы, но нужно отчетливо понимать, что документы " ручной работы" широко распространены. Чаще всего такие документы создают сами пользователи, но иногда эту работу необходимо выполнять и программисту. Созданию и работе с такими документами посвящено большое количество книг и статей, адресованных пользователям. Это отдельная и большая тема, выходящая за пределы рассматриваемых мной вопросов, но полностью обойтись без их рассмотрения не удается, поскольку это естественная часть работы офисного программиста. Поэтому я хочу рассмотреть один пример создания документа подобного рода, а главное, поговорить о том, что для таких документов иногда возможно создание программного проекта без явного программирования, используя возможности такого средства как упоминавшийся мной MacroRecorder.

    Создание электронного бланка "СЧЕТ - ФАКТУРА"

    Разработка электронной формы бланка заказа.

    В офисе работают с людьми и с документами. Оставим в стороне людей и займемся документами. В современном офисе документы имеют электронную форму. Давайте рассмотрим все этапы создания документа в электронной форме на примере бланка "СЧЕТ-ФАКТУРА" - типичного документа многих офисов. Будем создавать этот документ вручную, не прибегая к программированию, используя только стандартные возможности Office 2000. Конечно же, используя программирование на VBA, этот документ можно сделать куда более содержательным, например, связав его с базой данных. Но отложим рассмотрение этих вопросов на будущее и сосредоточимся на работе вручную и автоматическому получению макросов, запуск которых будет повторять работу, которую мы будем делать руками.

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

  • Начинайте разработку с создания его эскиза, если только не предусмотрен стандарт на форму этого бланка.
  • Выберите подходящий шаблон. Возможность быстрого создания документов на основе шаблонов - одно из главных преимуществ Office. Если такого шаблона нет, целесообразно его создать и уже на его основе разработать окончательный вид документа.
  • Создать удобный для пользователя документ в электронной форме удается, как правило, лишь с учетом замечаний пользователей, полученных в процессе работы с этим документом. Поэтому начинайте с прототипа. Если подходящий шаблон есть, документ, полученный как его копия, может с успехом служить прототипом.
  • Следуя этим советам, я создал эскиз бланка "СЧЕТ-ФАКТУРА". Конечно, для таких бланков есть зафиксированный стандарт. Я не стал строго следовать этому стандарту, поскольку преследую другие цели. Тем не менее, возможно, что бланк, который будет построен, может послужить шаблоном для создания "настоящего" бланка "СЧЕТ-ФАКТУРА". Наш бланк будет содержать:

  • Шапку, включающую логотип, название и реквизиты офиса (поставщика). Шапка - постоянная часть многих бланков офиса формируется автоматически, не требуя от пользователя, работающего с бланком, каких-либо действий; она может быть общей для многих бланков;
  • Реквизиты покупателя; эта переменная часть бланка заполняется при каждом оформлении заказа.
  • Данные о грузоотправителе и грузополучателе.
  • Таблица заказа - основная часть бланка заказа. Ее поля содержат сведения о заказываемом товаре: название, цену за единицу, количество заказанных единиц, общую стоимость и другие данные.
  • Утверждающие подписи
  • Пожалуй, достаточно. На этих составных частях нашего бланка мы пока и остановимся. С одной стороны, здесь есть все главные части, с другой стороны, подробное обсуждение того, как они создаются, потребует немалого времени. Чтобы легче воспринимать дальнейшее обсуждение, давайте взглянем на эскиз электронного бланка
  • (рис 6.5) Эскиз электронного бланка

    Конечно, внимательный читатель понял, что перед ним не "бумажный" рисунок, а реальный документ, разработанный средствами Office. Но в каком из приложений он создан? Мог ли он догадаться, что перед ним рабочая страница Excel? Аналогичный бланк можно было бы создать и в других приложениях Office таких, как Word и Access. Сейчас я подробно опишу, как был создан в Excel подобный шаблон, отступая, возможно, в некоторых деталях, так что окончательный результат будет немного отличаться от показанного на рисунке.

    Разработка шапки электронного бланка

    Как уже говорилось, шапка бланка будет включать логотип офиса, его название и другие реквизиты. Так как шапка может применяться при создании разных бланков, стоит разработать для нее отдельный шаблон. Более того, используя Macrorecorder, будет создан макрос, строящий эту шапку. Сразу же замечу, что это будет не один макрос, а серия небольших макросов, отражающих отдельные этапы работы. Я полагаю, что это правильная технология использования MacroRecorder. Макросы должны быть небольшими и соответствовать отдельным функциям, которые можно выделить естественным образом. В этом случае набор таких макросов допускает переиспользование и процесс создания нового шаблона во многом может свестись к сборке из набора готовых макросов. Мы пойдем по этой дороге, и созданные макросы помогут нам проследить за каждым шагом работы вручную, даже если мы и не опишем подробно его на словах.

    Выделим следующие этапы в создании шапки:

  • форматирование листа бланка на рабочем листе Excel;
  • рисование графического образа, задающего логотип - торговую марку офиса;
  • оформление названия офиса с помощью WordArt;
  • создание рамки, окаймляющей реквизиты офиса.
  • Форматирование листа бланка.

    Обычный лист Excel содержит "лишние" для бланка детали. Пользователь, работающий с бланком, может и не подозревать, что он работает с рабочим листом Excel, он должен видеть перед собой "обычный" лист с именованными полями, в которые заносится нужная информация. Поэтому я отключил такие элементы рабочего листа, как сетку, выделяющую ячейки, заголовки столбцов и строк, а также строку листа Excel, в которой отображаются имена ячеек и формулы, записанные в них. Это достигается установкой соответствующих переключателей в окне Options (Параметры) из меню Tools (Сервис). Следующим шагом работы является выделение области ячеек, соответствующих размеру электронного бланка и выделение ее подходящим цветом фона. На этом этапе я использовал возможности, предоставляемые для задания фона в окне Patterns (Вид) при выборе команды Cells (Ячейки) из меню Format (Формат). Замечу, что хотя совершенно естественно начинать описание процесса создания бланка с его форматирования, реально форматирование следует выполнять на п оследнем этапе, когда бланк полностью готов и осталось убрать лишние детали.

    Итак, вот моя программа действий по форматированию бланка с записью соответствующего макроса:

  • открыл чистый рабочий лист Excel;
  • вызвал запись макроса, которому дал имя "ФорматированиеБланка ";
  • удалил "лишние" элементы рабочего листа: сетку и заголовки строк и столбцов;
  • выделив область ячеек, соответствующую размеру бланка, залил ее подходящим цветом;
  • закончил запись макроса.
  • Приведу текст полученного макроса с добавленными мной комментариями:

    Sub ФорматированиеБланка()
    '
    ' ФорматированиеБланка Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    ' Этот макрос удаляет сетку, заголовки строк и столбцов, строку формул,
    	'показ нулей в ячейках рабочего листа. Выделяет подходящим фоном рабочую
    	'область бланка. В данном примере: A1:K56
    
    	Range("A1:K56").Select
    	With Selection.Interior
    		.ColorIndex = 15	'серый цвет
    		.Pattern = xlSolid
    		.PatternColorIndex = xlAutomatic
    	End With
    	With ActiveWindow
    		.DisplayGridlines = False	'сетка
    		.DisplayHeadings = False	'заголовки
    		.DisplayFormulas = False	'показ формул
    		.DisplayZeros = False		'отображение нулей
    	End With
    	Application.DisplayStatusBar = False	'строка статуса
    End Sub

    MacroRecorder помещает все создаваемые макросы в отдельный модуль, который я переименовал, назвав его "БланкСчетФактура".

    Создание логотипа

    Каждый приличный офис имеет логотип. Наш офис, который мы будем называть "Родная Речь" такой логотип имеет. Под логотипом мы понимаем графический образ, являющийся символом организации и размещаемый на ее бланках. Допустим, рисунок логотипа уже создан, и нам остается только поместить его в нужном месте бланка, возможно, согласовав размеры. Есть две возможности размещения его на бланке. При создании логотипа офиса "РР" я нашел рисунок бабочки и пририсовал в графическом редакторе на каждом из ее крылышек букву "Р", затем привел рисунок к нужным размерам. Чтобы поместить его на бланк, можно вставить рисунок в ячейку рабочего листа. Для этого нужно выделить подходящую ячейку и указать имя файла в окне, раскрывающемся при выборе команды From File (Из Файла) пункта Picture (Рисунок) меню Insert (Вставить). Но лучше идти более сложным путем: разместить на бланке элемент управления "Image", а затем, используя свойство Picture этого элемента, указать, где хранится рисунок. Несомненным преимуществом такого подхода является возможность использования рисунка произвольного размера. Меняя его свойства, можно его растянуть или сжать до приемлемых размеров. Кроме того, этот элемент управления реагирует на события и можно написать процедуру, которая, например, будет выдавать дополнительную информацию, скажем, о "главных" людях офиса, в тот момент, когда пользователь щелкнет кнопкой по области, занятой логотипом. Обращаем Ваше внимание на то, что при помещении на рабочий лист Excel элемента управления, он вставляется как OLE-объект и становится частью коллекции OLEObjects.

    Я выбрал путь, использующий OLE-объекты. Мои действия записывал макрос, который был назван "Логотип":

    Sub Логотип()
    '
    ' Логотип Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    	'Этот макрос создает элемент управления Image
    	'и помещает его в нужную позицию рабочего листа.
    
    	ActiveSheet.OLEObjects.Add(ClassType:="Forms.Image.1", Link:=False, _
    		DisplayAsIcon:=False, Left:=23.25, Top:=15, Width:=63, Height:=61.5). _
    		Select
    End Sub

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

  • Image получил имя Логотип;
  • включив свойство Picture, в появившемся окне я указал файл, содержащий образ с логотипом (бабочку РР);
  • свойству PictureSizeMode приписал значение 3 (PictureSizeModeZoom), при котором картинка распахивается на все окно, отведенное образу;
  • "Красивое имя"

    Рядом с логотипом поместим название офиса: "Издательство Родная Речь". Чтобы эта надпись выглядела незаурядно, воспользуемся возможностями, которые предоставляет инструментальная панель WordArt для художественного исполнения надписей. При написании слов "Издательство" и "Родная Речь" я использовал разные стили. С объектной точки зрения каждая из этих надписей представляет объект класса Shape. Я визуально менял свойства этих объектов, а Macrorecorder фиксировал мои действия в макросе с именем "КрасивоеИмя". Вот текст этого макроса:

    Sub КрасивоеИмя()
    '
    ' КрасивоеИмя Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    	'Этот макрос включает инструментальную панель WordArt
    	'и с ее помощью создает два рисованных объекта класса Shape
    	'первый из них задает слово "Издательство", второй -"Родная Речь"
    
    	ActiveSheet.Shapes.AddTextEffect(msoTextEffect10, "Издательство", _
    	"Arial Black", 24#, msoFalse, msoFalse, 177.75, 138.75).Select
    	Selection.ShapeRange.IncrementLeft -34.5
    	Selection.ShapeRange.IncrementTop -126#
    	ActiveSheet.Shapes.AddTextEffect(msoTextEffect4, _
    		"Р о д н а я	Р е ч ь ", _
    		"Impact", 20#, msoFalse, msoFalse, 197.25, 138.75).Select
    	Selection.ShapeRange.IncrementLeft -30#
    	Selection.ShapeRange.IncrementTop -80.25
    End Sub

    Формирование рамки с реквизитами офиса

    Заключительный шаг в формировании шапки - заполнение полей с реквизитами офиса: адресом, телефоном и др. Визуально эту операцию выполнить просто. В одной ячейке записывается название реквизита, а в ячейке справа его значение. Обычно одной ячейки для значения не хватает, и требуется расширить это поле до нужных размеров. Чтобы не менять размеры столбцов, проще всего слить идущие подряд ячейки. Вручную для этого нужно выделить ячейки и включить переключатель MergeCells (Объединение ячеек) на вкладке Alignment (Выравнивание) из пункта Cells (Ячейки) меню Format (Формат).

    Чтобы объединить поля с реквизитами и придать им некоторую структуру, их, как правило, заключают в рамку. Подходящую рамку можно выбрать из набора готовых Shape объектов, доступ к которым можно получить, включив инструментальную панель Рисование (Drawing) с элементами рисования; одна из ее кнопок - Автофигуры (AutoShapes) - позволяет добраться до базисных Shape-фигур. Когда рамка накладывается на поля с реквизитами, поля перестают быть видимыми. Рамку следует сделать прозрачной. Для этого нужно, выделив ее, щелкнуть правой кнопкой мыши, из контекстного меню выбрать пункт FormatAuto Shape и в появившемся окне в разделе заполнения (Fill) выбрать из списка значение для цвета NoFill (без заполнения). В этот момент при желании можно изменить стиль начертания границ рамки.

    Я проделал все описанные выше операции и получил законченную шапку, отвечающую нашему эскизу. Макрос "РеквизитыИРамка" позволит проследить за моими действиями последнего этапа - формирования реквизитов офиса. Заметьте, в завершение шапки я подвел черту, подчеркнув сформированную часть яркой линией.

    Sub РеквизитыИРамка()
    '
    ' РеквизитыИРамка Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    
    'Именование полей реквизитов
    	Range("D9:E9").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Адрес"
    	Range("D10:E10").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Телефон, Факс"
    	Range("D11:E11").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Email"
    	Range("D12:E12").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "ИНН"
    	
    	'Запись реквизитов
    	Range("F9:K9").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Москва, ул. Филевская, 15"
    	Range("F10:J10").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "175-3434, 175-3480"
    	Range("F11:J11").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "rr@red.ru"
    	Range("F12:J12").Select
    	Selection.MergeCells = True
    	Selection.HorizontalAlignment = xlLeft
    	ActiveCell.FormulaR1C1 = "198712345"
    	Range("F9:J12").Select
    	Selection.Font.Italic = True
    	
    	'Построение рамки
    	ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
    		105#, 96#, 369.75, 63.75).Select
    	Selection.ShapeRange.Fill.Visible = msoFalse
    	
    	'Линия отчеркивания
    	ActiveSheet.Shapes.AddLine(48.75, 189#, 475.5, 189#).Select
    	Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
    	Selection.ShapeRange.Line.Visible = msoTrue
    	Selection.ShapeRange.Line.Style = msoLineThinThin
    	Selection.ShapeRange.Line.Weight = 3#
    End Sub

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

    Макрос "Шапка"

    Мы выполнили всю работу по созданию шапки, разделив ее на четыре этапа. На каждом из этих этапов Macrorecorder создал соответствующий макрос. Осталось объединить эти макросы в один, - мы назвали его "Шапка". Он очень прост: последовательно вызывает уже созданные макросы каждого шага:

    Sub Шапка()
    	'Объединение четырех макросов для построения шапки бланка
    	ФорматированиеБланка
    	Логотип
    	КрасивоеИмя
    	РеквизитыИРамка
    End Sub

    Запустив этот макрос на чистом листе, мы получим заполненную шапку. Точнее, почти заполненную, поскольку макрос "Логотип" не записал действий с объектом класса Image по изменению его свойств. Так что нужно приложить некоторые усилия и повторить действия по установлению свойств объекта, чтобы увидеть картинку, связанную с объектом Image. Взгляните на результат работы макроса "Шапка":

    (рис 6.6) Построение шапки

    Реквизиты покупателя

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

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

    Sub РеквизитыЗаказчика()
    '
    ' РеквизитыЗаказчика Macro
    ' Macro recorded 28.11.1999 by Vladimir Billig
    
    '	Именование бланка
    	Range("C16:I16").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "СЧЕТ-ФАКТУРА №					от "
    	With ActiveCell.Characters(Start:=1, Length:=31).Font
    		.FontStyle = "Полужирный"
    		.Size = 14
    	End With
    	With ActiveCell.Characters(Start:=32).Font
    		.FontStyle = "Полужирный Курсив"
    		.Size = 11
    	End With
    	'Задание полей реквизитов заказчика
    	Range("B19:C19").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Покупатель"
    	Range("B20:C20").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Адрес"
    	Range("B21:C21").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Тел., Факс, Email"
    	Range("B22:C22").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "ИНН"
    	Range("D19:J19").Select
    	Selection.MergeCells = True
    	Range("D20:J20").Select
    	Selection.MergeCells = True
    	Range("D21:J21").Select
    	Selection.MergeCells = True
    	Range("D22:J22").Select
    	Selection.MergeCells = True
    	'Создание рамки
    	ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
    		34.5, 231.75, 452.25, 63.75).Select
    	Selection.ShapeRange.Fill.Visible = msoFalse
    	'Надпись на рамке
    	ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, _
    		95, 225, 60, 15).Select
    	Selection.Characters.Text = "Покупатель"
    	
    	'Реквизиты грузоотправителя и грузополучателя
    	Range("B25:D25").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Грузоотправитель и его адрес"
    	Range("E25:J25").Select
    	Selection.MergeCells = True
    	Range("B26:D26").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Грузополучатель и его адрес"
    	Range("E26:J26").Select
    	Selection.MergeCells = True
    	Range("B25:J26").Select
    	Selection.Font.FontStyle = "Полужирный Курсив"
    	Selection.Font.Size = 9
    	'Отчеркивание
    	ActiveSheet.Shapes.AddLine(44.25, 357.75, 500.25, 357.75).Select
    	Selection.ShapeRange.Line.Style = msoLineThinThin
    	Selection.ShapeRange.Line.Weight = 3#
    	Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
    	Selection.ShapeRange.Line.Visible = msoTrue
    End Sub

    Запустив макрос "РеквизитыЗаказчика", на рабочем листе с уже созданной шапкой я получил следующую часть бланка:

    (рис 6.7) Шапка и раздел с реквизитами заказчика

    Раздел "Таблица заказа"

    Основная часть этого бланка - таблица со сведениями о заказываемых товарах. Сетка, которая обычно очерчивает границы ячеек рабочего листа, была удалена, на электронном бланке заказа она неуместна. Теперь необходимо восстановить некоторые из этих границ, чтобы нарисовать таблицу в привычной для глаз форме. На этом этапе я буду работать с вкладкой Borders ("Границы"), открываемой в окне Format Cells ("Формат ячеек") из меню Format ("Формат"). С объектной точки зрения границы объекта класса Range составляют коллекцию Borders. Меняя свойства элементов этой коллекции (объектов Border), можно добиться нужного эффекта. Я построю таблицу в три этапа:

  • столбцы таблицы;
  • шапку таблицы с заголовками полей;
  • последнюю, итоговую строку.
  • Такое разделение сделает обозримыми макросы, транслирующие мои действия в тексты на VBA.

    Построение столбцов

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

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

    Sub СтолбцыТаблицы()
    '
    ' СтолбцыТаблицы Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    
     'Изменение ширины полей, меняя размеры столбцов
    	Columns("E:E").ColumnWidth = 4
    	Columns("F:F").ColumnWidth = 4
    	Columns("I:I").ColumnWidth = 4.43
    	Columns("K:K").ColumnWidth = 11.86
    
    	'Слияние ячеек для поля Название Товара
    	Range("A32:D32").Select
    	Selection.MergeCells = True
    	'Выделение внешних границ: слева,снизу, справа
    	Range("A32:K32").Select
    	With Selection.Borders(xlEdgeLeft)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	With Selection.Borders(xlEdgeBottom)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	With Selection.Borders(xlEdgeRight)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	'Выделение вертикальной внутренней границы
    	With Selection.Borders(xlInsideVertical)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	'Копирование формата на всю область таблицы
    	 Selection.Copy
    	Range("A33:K46").Select
    	Selection.PasteSpecial Paste:=xlFormats, Operation:=xlNone, _
    		SkipBlanks:=False, Transpose:=False
    	Application.CutCopyMode = False
    End Sub

    Шапка таблицы

    Шапка таблицы будет состоять из двух строк, в первой содержатся названия полей, во второй их индексы. Используя соответствующие атрибуты на вкладке Alignment (Выравнивание), я задал центрирование текста по вертикали и горизонтали, а также автоматическое изменение высоты строки, чтобы текст названия полей был полностью видимым. Кроме того, я выделил графически границы шапки, задав их двойными линиями. Макрос получается, конечно, большим, поскольку оперирует с большим числом объектов. Вот его текст:

    Sub ШапкаТаблицы()
    '
    ' ШапкаТаблицы Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    
    	'Именование полей таблицы и задание индексов
    	Range("A32:D32").Select
    	ActiveCell.FormulaR1C1 = "Наименование товара"
    	Range("E32").Select
    	ActiveCell.FormulaR1C1 = "Единица измерения"
    	Range("F32").Select
    	ActiveCell.FormulaR1C1 = "Количество"
    	Range("G32").Select
    	ActiveCell.FormulaR1C1 = "Цена"
    	Range("H32").Select
    	ActiveCell.FormulaR1C1 = "Сумма"
    	Range("I32").Select
    	ActiveCell.FormulaR1C1 = "Ставка НДС"
    	Range("J32").Select
    	ActiveCell.FormulaR1C1 = "Сумма НДС"
    	Range("K32").Select
    	ActiveCell.FormulaR1C1 = "Всего с НДС"
    	Range("A33:D33").Select
    	ActiveCell.FormulaR1C1 = "1"
    	Range("E33").Select
    	ActiveCell.FormulaR1C1 = "2"
    	Range("F33").Select
    	ActiveCell.FormulaR1C1 = "3"
    	Range("G33").Select
    	ActiveCell.FormulaR1C1 = "4"
    	Range("H33").Select
    	ActiveCell.FormulaR1C1 = "5"
    	Range("I33").Select
    	ActiveCell.FormulaR1C1 = "6"
    	Range("J33").Select
    	ActiveCell.FormulaR1C1 = "7"
    	Range("K33").Select
    	ActiveCell.FormulaR1C1 = "8"
    	
    	'Центрирование текста и изменение высоты строки,
    	 'обеспечивающее видимость текста
    	Range("A32:K33").Select
    	With Selection
    		.HorizontalAlignment = xlCenter
    		.VerticalAlignment = xlCenter
    		.WrapText = True
    	End With
    	
    	'Выделение границ шапки
    	With Selection.Borders(xlEdgeLeft)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeTop)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeBottom)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeRight)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlInsideVertical)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	
    	
    End Sub

    Последняя строка

    Последняя строка, как и строки шапки, отличается от остальных строк таблицы. Она используется для подведения итогов. Также как и шапку, я выделил ее границы графически. Итак, макрос "ПоследняяСтрока":

    Sub ПоследняяСтрока()
    '
    ' ПоследняяСтрока Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    
    	'Задание итоговой строки
    	Range("A46:D46").Select
    	ActiveCell.FormulaR1C1 = "Всего к оплате"
    	Range("A46:K46").Select
    	With Selection.Font
    		.Name = "Arial"
    		.FontStyle = "Полужирный"
    		.Size = 11
    	End With
    	
    	'Выделение границ итоговой строки
    	With Selection.Borders(xlEdgeLeft)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeTop)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeBottom)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeRight)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlInsideVertical)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    End Sub

    Задание расчетных формул и форматирование полей

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

    Sub Расчеты()
    '
    ' Расчеты Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    
    	'Форматирование полей и задание расчетных формул
    	'Форматирование поля "Название товара"
    	Range("A34:D45").Select
    	Selection.ShrinkToFit = True
    	
    	'Форматирование поля "Единица Измерения"
    	Range("E34:E45").Select
    	Selection.HorizontalAlignment = xlCenter
    	'Форматирование поля "Цена"
    	Range("G34:G45").Select
    	Selection.NumberFormat = "0.00"
    	'Форматирование поля "Сумма"
    	Range("H34:H46").Select
    	Selection.NumberFormat = "0.00"
    	'Формула: Сумма = Цена * Количество
    	Range("H34").Select
    	ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
    	'Копирование формулы
    	Range("H34").Select
    	Selection.AutoFill Destination:=Range("H34:H45"), Type:=xlFillDefault
    	Range("H34:H45").Select
    	'Итоговая сумма
    	Range("H46").Select
    	ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
    	
    	'Форматирование поля "НДС"
    	Range("I34:I45").Select
    	Selection.NumberFormat = "0%"
    	
    	'Форматирование поля "Сумма НДС"
    	Range("J34:J46").Select
    	Selection.NumberFormat = "0.00"
    	'Формула: Сумма НДС = Сумма * НДС
    	Range("J34").Select
    	ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
    	'Копирование формулы
    	Range("J34").Select
    	Selection.AutoFill Destination:=Range("J34:J45"), Type:=xlFillDefault
    	Range("J34:J45").Select
    	'Итоговая сумма
    	Range("J46").Select
    	ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
    	
    	'Форматирование поля "Всего с НДС"
    	Range("K34:K46").Select
    	Selection.NumberFormat = "0.00"
    	'Формула: Всего с НДС = Сумма + Сумма НДС
    	Range("K34").Select
    	ActiveCell.FormulaR1C1 = "=RC[-3]+RC[-1]"
    	'Копирование формулы
    	Range("K34").Select
    	Selection.AutoFill Destination:=Range("K34:K45"), Type:=xlFillDefault
    	Range("K34:K45").Select
    	'Итоговая сумма
    	Range("K46").Select
    	ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
    	Range("K47").Select
    	
    	'Нулевые значения в таблице не отображаются
    	ActiveWindow.DisplayZeros = False
    End Sub

    Я собрал макросы, строящие отдельные части таблицы под одной обложкой в макросе "ТаблицаРасчеты":

    Sub ТаблицаРасчеты()
      'Построение таблицы
      СтолбцыТаблицы
      ШапкаТаблицы
      ПоследняяСтрока
      Расчеты
    End Sub

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

    (рис 6.8) Таблица продажи товаров бланка Счет-Фактура

    Заключительный макрос "УтверждающиеПодписи"

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

    Sub УтверждающиеПодписи()
    '
    ' УтверждающиеПодписи Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    	Range("B50:G50").Select
    	Selection.MergeCells = True
    	With Selection.Font
    		.Name = "Arial"
    		.FontStyle = "Полужирный Курсив"
    		.Size = 11
    	End With
    	ActiveCell.FormulaR1C1 = "Ген. Директор _________________________"
    	
    	Range("B52:G52").Select
    	Selection.MergeCells = True
    	With Selection.Font
    		.Name = "Arial"
    		.FontStyle = "Полужирный Курсив"
    		.Size = 11
    	End With
    	Range("B52:G52").Select
    	ActiveCell.FormulaR1C1 = "Гл. Бухгалтер __________________________"
    	Range("B55").Select
    	ActiveCell.FormulaR1C1 = "М. П."
    End Sub
    Сборка макросов. Макрос "СчетФактура"
    Для завершения работы и получения макроса, который строит шаблон бланка "Счет-Фактура", осталось собрать все макросы, строящие отдельные части бланка. Вот текст заключительного макроса:
    Sub СчетФактура()
    	'Этот заключительный макрос строит шаблон бланка Счет-Фактура
    	'Он вызывает макросы, строящие отдельный части этого бланка
    	Шапка
    	РеквизитыЗаказчика
    	ТаблицаРасчеты
    	УтверждающиеПодписи
    End Sub

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

    Шаблон бланка построен. Что дальше?

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

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

    При создании в Excel бланка "Счет-Фактура" MacroRecorder позволил получил макросы, записавшие практически все действия пользователя, создававшего этот бланк вручную. Единственное, что не позволил MacroRecorder, это проследить за действиями при работе с элементом управления, помещенным на бланк. Я уже говорил о том, что программисту полезно обращаться к MacroRecorder не только для того, чтобы получить соответствующий макрос, который он, возможно, использует в дальнейшем, чтобы не повторять действия вручную. Часто он обращается к MacroRecorder в процессе обучения работе с объектами Office 2000. В тех случаях, когда программист не знает с каким объектом ему лучше работать при решении той или иной задачи, какими свойствами и методами объекта следует пользоваться, то часто полезно обратиться к MacroRecorder, записать макрос и посмотреть, как MacroRecorder решает данную задачу. Например, задачей программиста является создание системы, позволяющей проводить оптимизацию инвестиций. Чтобы понять, как и с какими объектами Excel следует работать в данной ситуации, рекомендуется решить вручную несколько оптимизационных задач, записав при этом соответствующие макросы.

    (рис 6.9) Построение таблицы в документе Word

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

    Не стоит особенно печалиться по поводу сравнительно небольших возможностей MacroRecorder. Это лишь один из вспомогательных инструментов, используемых в офисном программировании. Главное, что есть в распоряжении программиста, работающего в среде Office 2000, - язык VBA, набор объектов из библиотек Office 2000, возможность использовать компоненты, созданные на других языках и в других программных средах, используя все эти средства можно решить практически все задачи, возникающие в жизни офиса при работе с его документами. На этом я и закончу введение в офисное программирование.

    Страницы:

    Особенности офисного программирования

    В чем же специфика офисного программирования, чем оно отличается от программирования в программной среде таких языков как VB, VC++ или Delphi, ориентированных на создание программных проектов различного типа? Выделим характерные особенности:

  • Среда разработки. Мощная и разнообразная среда приложений Office, в которой можно создавать документы разного типа и работать с ними. Поскольку эта среда ориентирована в первую очередь не на программистов, а на пользователей, то в ней можно создавать документы без всякого программирования. Поэтому программист обычно начинает не на пустом месте, он начинает работать с документами, их заготовками, созданными пользователями. Заметьте, что и сам программист может выступать в роли пользователя и сочетать в своей работе традиционное программирование с работой "вручную", без программирования. При программировании офисных документов сама среда представлена в виде объектов, свойства, методы и события которых доступны в языке программирования VBA.
  • Совместная работа. В работе над документами Office могут естественным образом сотрудничать как программисты, так и пользователи - специалисты, работающие с документом, возможно, создающие его руками, но не занимающиеся программированием. Мне кажется, что здесь может быть преодолен всегда существовавший барьер между разработчиками программы и ее пользователями. Совместная, тесная работа над документами специалиста в некоторой предметной области и программиста-профессионала характерна для офисного программирования и реально может приводить к качественным эффектам, уменьшая время разработки этих документов и улучшая их качество. При совместной работе над документами может быть существенно сокращен типичный для программных продуктов цикл разработки, включающий такие этапы, как создание прототипа системы и его бета - тестирование.
  • Цели разработки.Может быть, именно с этого пункта и следовало начинать, говоря об особенностях офисного программирования. Дело в том, что меняются цели, приоритеты, сам взгляд на сущность работы программиста, работающего в среде Office. Ранее целью программиста было создание приложения, понимаемого как программа, программный проект. Теперь программист является одним из участников (возможно единственным) создания системы документов. Документ, а не программа, становится целью разработки. Программный проект - это лишь часть документа. В Office программный проект неразрывно связан с документом, хранится, как часть документа, и не может существовать независимо от него.
  • Настройка документов. Слово "Настройка" (Custom-built) является одним из ключевых слов в офисном программировании. Действительно, стандартные возможности среды по созданию и работе с документами велики. Однако, возможность настроить стандартный документ Office, сделать его "по заказу", снабдить его новыми функциями, учитывающими специфику решаемой задачи, подогнать его "под себя" это одна из важнейших особенностей офисного программирования. Настройка может быть очень простой и состоять в том, что стандартный документ получает некоторые полезные дополнительные свойства, расширяющие его возможности или внешний вид. Обычно так начинают свой путь в офисное программирование продвинутые пользователи Office. Но настройка может быть очень сложной и документ, сделанный по заказу, может ничем не напоминать обычный стандартный документ Office.
  • Каркас документа.Для программиста сам Office - это ни что иное, как обычная совокупность библиотек классов. В самом этом факте нет ничего специфического. Без библиотек классов не обходится ни одна современная среда программирования. Такие библиотеки классов составляют каркас приложений (Framework Applications). Типичным примером является библиотека MFC в языке VC++. Работа программиста в такой среде начинается с создания каркаса своего приложения (Framework Application) на основе классов, выбираемых из библиотеки каркаса приложений. Заметьте, по написанию "каркас приложений" и "каркас приложения" различаются лишь одной буквой, но эти два понятия различны по своей сути. Также как и для других программных сред, библиотеки классов Office представляют собой каркас приложений, или, что может быть точнее с содержательной точки зрения, - каркас документов (Framework Documents). Эти библиотеки классов Office содержат каркасы офисных документов - текстовых документов, документов, основу которых составляют электронные таблицы, презентации, базы данных. И хотя библиотека Office 2000 и библиотека MFC не сравнимы между собой по ряду параметров, поскольку у них все-таки разные цели, но в определенной степени библиотека Office 2000 гораздо богаче библиотеки MFC. Она позволяет создавать документы самых разных типов, обладающих с момента рождения весьма широкими возможностями. Всякий раз, когда создается новый документ, его каркас по умолчанию составляют объекты библиотек, отобранных по умолчанию для построения этого конкретного каркаса документа. Но одно из достоинств офисного программирования состоит в том, что этот "каркас по умолчанию" можно существенно изменить, добавив в документ новые свойства. Для этого достаточно включить в состав каркаса соответствующие библиотеки из числа тех, что составляют каркас документов Office. Заметим, что в Office 2000 число таких дополнительно поставляемых библиотек, а, следовательно, и набор возможностей, существен вырос по сравнению c предыдущей версией. Расширение каркаса документа не требует от программиста никаких значительных усилий, достаточно в редакторе Visual Basic выбрать пункт меню References и в появившемся списке всех возможных библиотек, включить те, которые отвечают его индивидуальным потребностям.
  • Язык программирования + Мир объектов. Программист, занимающийся настройкой офисных документов, как и всякий программист должен владеть языком программирования и таковым для него является язык VB. С другой стороны для офисного программиста не менее важно знать или, по крайней мере, хорошо ориентироваться в мире объектов Office, число которых выходит за пределы, доступные запоминанию. Единственно, что здесь помогает, это разумно сделанная в среде система помощи - сюда входит и браузер объектов, и справочная система, и система интеллектуальной поддержки Intellisence. Так что к программисту, занимающемуся офисным программированием, предъявляются дополнительные требования; помимо языка программирования он должен изучить мир объектов среды. Более того, крайне полезно уметь работать с этими объектами вручную так, как это делают обычные пользователи Office. Два слова хочу сказать о VBA, представляющий, как уже было сказано, язык VB, встроенный в среду Office. Заметьте, фирма Microsoft сделала революционный шаг, - она не стала создавать в среде Office 2000 собственный язык программирования, как это делалось ранее в большинстве известных сред (Oracle, FoxPro, AutoCad), а встроила в среду язык, уже известный программистам. Важно и то, что язык VBA является отчуждаемым от среды и может быть встроен в различные среды. Так что язык VBA в этом отношении становится схож с естественным языком, встраиваемым во все профессиональные области знания. Конечно, было бы совсем хорошо, если бы среда позволяла работать с любым известным языком программирования, так что программисту привыкшему работать с языком С++ или привыкшему к объектам Паскаля, принятым в Delphi, не пришлось бы переучиваться и привыкать к VBA.
  • MacroRecorder. Еще одна интересная особенность офисного программирования состоит в возможности создания программного проекта или, по крайней мере, его отдельных компонент автоматически без программирования. Эта возможность основана на использовании такого характерного для офисного программирования инструмента как MacroRecorder. MacroRecorder это транслятор действий, записывающий действия пользователя при работе вручную и транслирующий их в программу на языке VBA. Пользователь, работающий "вручную" в среде Office, видит зримые образы объектов среды абзацы в документах Word, ячейки в документах Excel, таблицы в документах Access, слайды в документах Power Point, папки в документах Outlook, формы с их элементами управления во всех этих документах и многие другие объекты. Пользователь может работать с зримыми образами этих объектов, вводить текст абзаца, задавать формулу в ячейке, работать с таблицами и папками, нажимать кнопки и выбирать элементы из раскрывающихся списков. Заметьте, реальный мир Office это мир его объектов. Пользователь не знает реального мира, он работает в мире образов, но система, как внимательный наблюдатель следит за всеми действиями пользователя и в ответ на них изменяет свойства объектов, вызывает обработчики событий, реагируя на возникающие события, вызывает методы объектов Office. Поскольку все действия пользователя транслируются в действия над объектами Office, то нетрудно, включив MacroRecorder, записать нужные действия и создать макрос программу на языке VBA, описывающую действия пользователя в терминах работы с объектами. Сегодня возможности MacroRecorder ограничены, он не очень интеллектуален и не может распознать ошибочные действия пользователя и их последующие исправления, он не может транслировать действия пользователя при его работе с целым рядом объектов, например, объектами из коллекции Shapes, встраиваемыми в документы Office 2000.

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

    К сожалению, Microsoft все в меньшей степени поддерживает MacroRecorder в своих новых разработках.

  • Два крайних случая. Интересно рассмотреть два крайних случая, возникающих при работе с офисными документами. Среди всех офисных документов выделим два типа документов, которые будем условно называть документами типа "обложка" и документами типа "ручная работа". В первом случае под обложкой документа Office содержится обычный программный проект на языке VBA, не использующий, или почти не использующий обращений к объектам Office. Типичным классом таких документов могут служить игры, - карточные и другие, реализованные в Office, но никак не ориентированные на его специальные возможности. Другая причина в создании таких документов - обложек может быть еще проще. Программист привык к этой удобной среде и создает нетипичный для среды документ, используя привычный для него язык VBA. Документы типа "ручная работа" распространены в гораздо большей степени. Такими являются все документы, создаваемые пользователями Office, не использующими VBA. Большинство начинающих да и давно работающих пользователей вполне удовлетворены стандартным набором возможностей и могут даже не подозревать о тех скрытых возможностях, которые открывает для них офисное программирование. Заметьте, что подобные документы могут быть частью "серьезных проектов", когда средствами офисного программирования создается система документов для решения сложных прикладных задач. В этом и состоит мощь среды Office, что решение многих задач, возникающих в процессе деятельности организации, не требует специального программирования и может быть получено "вручную". Самым простым и типичным примером является работа с различными бланками при автоматизации деятельности офиса. Первым шагом в создании документа - бланка является его построение. Понятно, что работу по построению бланка в электронном формате проще всего и удобнее всего осуществить "вручную" в том же Excel или Word. Такие документы являются несомненной частью общей системы документов. Говоря о таких документах следует добавить, что офисный программист, чаще всего, включит MacroRecorder, фиксируя процесс создания подобного документа. Более того, он разобьет весь процесс на сравнительно небольшие шаги. Полученные макросы затем могут быть оптимизированы, унифицированы и использованы при построении подобных документов уже программным путем, избавляя квалифицированного пользователя от повторения работы, которую можно считать рутинной, коль скоро есть соответствующий макрос, решающий эту задачу. Более того, в инструментарии программиста, работающего в этой области, может быть программа, позволяющая собирать новый бланк из уже имеющихся заготовок, примерно так, как собирается "puzzle".
  • Office 2000 - платформа разработчика. Спектр применения офисного программирования широк - от настройки отдельных документов до создания серьезных решений масштаба предприятия. Благодаря интеграции с семейством серверных продуктов Microsoft, интеграции с продуктами третьих фирм, целью разработки становится создание корпоративных приложений, нацеленных на совместную работу в Internet.
  • Подведем теперь некоторые итоги и постараемся ответить на два главных вопроса:

  • Что дает офисное программирование пользователям?
  • Что дает офисное программирование программистам?
  • Пользователям и программистам

    Офисное программирование пользователям

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

    "Работать с документами Office 2000, не используя VBA, все равно, что играть на ненастроенном пианино. Играть, конечно, можно, но профессионал этого позволить себе не может".

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

    Однако, мое сравнение, как и всякое сравнение "хромает". Его можно интерпретировать и по-другому: "Фирма Microsoft предлагает своим пользователям ненастроенные документы ("расстроенное пианино"). Чтобы с ними работать, нужно их предварительно настраивать". Дабы избежать подобных заключений, несправедливых по отношению к этому прекрасному продукту, я теперь предпочитаю в подобных ситуациях говорить так:

    "Работать с документами Office 2000 все равно, что играть на хорошо настроенном пианино. Но у этого инструмента есть возможности, открывающиеся для профессионалов. Нажмите ALT + F11 и создайте собственную композицию".

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

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

    А есть ли преимущества у программиста, работающего в Office? Есть, и их, пожалуй, не меньше, чем у пользователя:

  • В его распоряжении мощная интегрированная среда. Для программиста эта среда представлена в виде совокупности хорошо организованных объектов, доступных в языке программирования и по принципу работы ничем не отличающихся от встроенных объектов языка или объектов, создаваемых самим программистом.
  • Большинство повседневных задач становятся для него простыми, - чтобы их решить, зачастую достаточно стандартных средств.
  • Там, где стандартных средств не хватает, где у документа должны появится новые функциональные возможности, где необходимо создать документ по заказу вступает в силу язык программирования - VBA, существенная особенность которого - возможность работы с объектами любого из приложений Office.
  • Office 2000- это среда разработки, отвечающая современному принципу: "Простые задачи должны решаться просто". Мощность среды определяется тем, какие задачи для нее являются простыми. В этом отношении Office 2000 уникален - круг "простых" для него задач весьма широк. Задачи, традиционно считающиеся сложными, программист может успешно решить в среде Office 2000.
  • Если говорить о сложных задачах, то, естественно, есть такие задачи, для которых стандартных средств Office 2000 и языка VBA недостаточно. Вообще следует приветствовать одновременное существование различных программных сред, операционных систем, любимых теми или иными программистами. Лучшая позиция программиста должна состоять не в противопоставлении программных продуктов различных производителей, а в их совместном использовании. Именно поэтому мне кажутся важными идеи компонентного программирования, где вырабатывается стандарт на взаимодействие компонент, создаваемых в разных программных средах, на разных языках, на разных платформах и находящихся на разных машинах. Компонентный подход одна из характерных особенностей офисного программирования. Работа с компонентами DLL, ActiveX, AddIns, ComAddIns все это неотъемлемая часть арсенала офисного программирования.
  • Офисное программирование это развивающееся направление в программировании, так что у программистов есть все возможности внести свой вклад в создание технологий и приемов работы с офисными документами. Одним из наиболее перспективных таких направлений, развиваемых в Office 2000, несомненно, является работа с документами, опубликованными в Internet и Intranet. Web-страницы становятся тем рабочим пространством, где члены рабочей группы совместно работают над документами Office 2000.
  • Заключая этот параграф, хочется еще раз напомнить пользователям и программистам, что именно их совместная работа над офисными документами может дать наилучшие результаты. В этом состоит одна из главных особенностей офисного программирования.

    Реализация известной игры или создание документа - обложки.

    Давайте перейдем от лозунгов и призывов к конкретным делам. Для начала рассмотрим создание документа - обложки. Как мы говорили ранее, это некоторый специальный вид офисных документов, в которых не требуется знания объектов Office 2000. Такой документ представляет программный проект под обложкой одного из офисных документов, причем не важно какую из обложек выбрать - Word, Excel или, например, Power Point. Создается такой документ почти также как и программный проект в любой визуальной среде. По этой причине он достаточно легко переносится в любую из этих сред. Типичным примером таких документов, как мы говорили, являются игры. Рассмотрим посему реализацию хорошо известной игры "Волк, Коза и Капуста". Для понимания реализации нужно знать основы VBA и работу с интерфейсными объектами формами и их элементами.

    Игра "Волк, Коза и Капуста"

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

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

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

    В нашей игре есть 8 основных объектов: Человек, Волк, Коза, Капуста, Река, Левый Берег, Правый Берег, Лодка. Я добавил еще один объект - Акулу, чтобы реку было страшнее переплывать. Эти объекты будут иметь видимые образы, манипулируя которыми игрок добивается достижения поставленной цели, доставить человека, козу, волка и капусту с левого на правый берег. Полем, где будет разворачиваться действие игры, будет обычная форма VBA, она будет образом объекта Река. На реке (форме) я размещу образы других объектов игры. Каждый из этих объектов будет задаваться объектом VBA класса Image из библиотеки классов MSForms.

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

    Перед программистом, реализующим эту игру, стоят две основные задачи:

  • Спроектировать визуальный интерфейс игры, форму с ее объектами.
  • Написать обработчики событий для интерфейсных объектов.
  • Первую из этих задач он, скорее всего, как это сделал я, будет решать руками, визуально проектируя интерфейс, для решения второй задачи ему потребуется знание языка VBA и интерфейсных объектов. Знание объектов Office 2000 (объектов Word, Excel, Power Point) ему не понадобится, под какой бы документной обложкой не был реализован этот программный проект.

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

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

    (рис 6.1) Форма в начальном состоянии в игре "Волк, Коза и Капуста"

    Проектирование интерфейса

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

  • Создание формы - образа объекта Река. Итак, я начал с того, что нажал ALT +F11 и попал в среду Редактора VBA. В меню Insert я выбрал пункт UserForm, получил заготовку формы и приступил к заданию ее свойств. Первым делом я подобрал подходящие размеры формы. Это можно делать руками, но, в тех случаях, когда желательно задать точные, целочисленные значения, можно задать значения свойств Height и Width. Я изменил имя формы, назвав ее WGCForm, и дал заголовок "Wolf, Goat and Cabbage". Главное, я изменил свойство BackColor, выбрав из палитры цвет, подходящий для речной глади. Теперь форма стала похожей на реку. Еще одно серьезное изменение свойств состояло в том, что форму я сделал немодальной, изменив свойство ShowModal. Форма VBA является, как известно, обычным диалоговым окном. Всякое такое окно может быть модальным или немодальным. Из модальных окон нельзя выйти, не закончив диалога. Если же форму сделать немодальной, то это важное свойство формы позволит выходить из нее, не завершив диалога, зани маться другими делами, а потом возвращаться в форму. В частности, это позволяет во время игры при необходимости читать документ Word или создавать его, как это делаю я, имея форму перед глазами.
  • Создание основных объектов и их размещение. На следующем шаге я разместил на форме 8 одинаковых элементов управления объектов класса Image из стандартной панели инструментов ToolBox. Осталось придать им подходящий внешний вид:
  • Два объекта представляют два берега реки. Я назвал их, задав свойство Name, соответственно LeftBank и RightBank и разместил по краям реки. Никакие картинки с этими объектами можно не связывать, достаточно выбрать подходящий цвет фона и размеры.
  • У левого берега реки я поместил объект, который получил имя Boat, и связал с ним соответствующую картинку лодки. Еще один объект Shark с картинкой акулы я разместил в реке. Он введен больше для антуража и как будущий возможный герой при расширении игры. Вместе с тем он будет играть и некоторую полезную роль, позволяя в любой момент начать игру с начала.
  • Четыре объекта размещены на левом берегу реки это наши герои: человек, волк коза и капуста. Конечно, для этих элементов необходимо задать свойство Picture так, чтобы соответствующая картинка соответствовала образу объекта. Благо с этим теперь особых проблем нет, так как галерея ClipArt, поставляемая с Office 2000, содержит тысячи различных картинок. Проблема состоит лишь в том, чтобы из этого изобилия выбрать подходящую картинку. Напомню, что поиск в галерее ClipArt возможен по ключевому слову. Задайте, например, "goats" для поиска картинки с изображением козы, учитывая, что при задании ключевого слова лучше использовать множественное число. Итак, я нашел картинки, подогнал их под выбранные размеры Image-элементов, установив подходящее значение свойства PictureSizeMode. Мои объекты получили соответствующие имена: Man, Wolf, Goat and Cabbage.
  • Обратите внимание, порядок размещения Image-элементов имеет значение, поскольку при их частичном совмещении один из них (более поздний) перекрывает другой. Поскольку такая ситуация будет иметь место, когда герои игры собираются в лодке, то целесообразно вначале на форме разместить лодку, а изображение человека разместить последним. Результат моих действий показан на предыдущем рисунке. При некотором навыке, когда картинки под рукой, все действие по формированию интерфейса занимает 10 -15 минут. У меня на это ушло больше времени, так как из-за отсутствия способностей к рисованию проектирование интерфейса это не самая сильная моя сторона. И здесь как раз тот самый случай, когда такие задачи должен решать специалист - дизайнер, а не программист.

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

    Программирование игры

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

  • Введение программных объектов и их инициализация.
  • Посадка пассажиров в лодку.
  • Высадка на берег
  • Переправа через реку.
  • Тестирование ситуаций.
  • Организация диалогов
  • Будем следовать этой схеме при описании реализации игры.

    Программные объекты и их инициализация

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

    Для реализации игры достаточно ввести 5 переменных, следящих за состоянием перемещаемых объектов: человека, волка, козы, капусты и лодки. Каждый из них может находиться в трех состояниях, быть на левом берегу, правом берегу или в лодке (у объекта Boat всего два состояния). Еще одна переменная будет следить за числом пассажиров в лодке. Вот описания из раздела Declarations стандартного модуля с именем WGCModule, который я создал первым делом:

    Option Explicit
    'Состояния наших объектов - человека, волка, козы, капусты и лодки
    Public StateOfMan As String
    Public StateOfWolf As String
    Public StateOfGoat As String
    Public StateOfCabbage As String
    Public StateOfBoat As String
    Public CountInBoat As Byte	'Число пассажиров в лодке
    Public WidthOfRiver	'Ширина реки, используемая при переправе

    Приведем теперь процедуру InitialStates, которая задает начальные установки для наших переменных и визуальных объектов:

    Public Sub InitialStates()
    	'Задаем начальные состояния объектов и их образов
    	StateOfMan = "LeftBank"
    	StateOfWolf = "LeftBank"
    	StateOfGoat = "LeftBank"
    	StateOfCabbage = "LeftBank"
    	StateOfBoat = "LeftBank"
    	CountInBoat = 0
    	
    	With WGCForm
    		.Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10
    		.Man.Visible = True
    		.Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10
    		.Wolf.Visible = True
    		.Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10
    		.Goat.Visible = True
    		.Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10
    		.Cabbage.Visible = True
    		
    		.Boat.Top = .LeftBank.Top + 130: .Boat.Left = .LeftBank.Left + _ .LeftBank.Width
    		.Boat.Visible = True
    		WidthOfRiver = .Width - .LeftBank.Width - .RightBank.Width _
    			- .Boat.Width
    	End With
    	
    
    End Sub

    Процедура InitialStates будет вызываться в ответ на такие события, как инициализация или активация нашей формы:

    Private Sub UserForm_Activate()
    	InitialStates
    End Sub
    
    Private Sub UserForm_Initialize()
    	InitialStates
    End Sub

    Как посадить пассажира в лодку одним щелчком

    Рассмотрим вначале самый простой способ посадки пассажира в лодку. Щелчок левой кнопки мыши на видимом объекте образе одного из наших героев будет перемещать его (образ) в лодку. При этом, естественно, для того, чтобы перемещение было допустимым, нужно чтобы герой и лодка находились на одном берегу. Программно перемещение объекта в лодку делается чрезвычайно просто, для этого достаточно изменить свойства Top и Left, дав им новые значения, зависящие от значения этих свойств объекта Boat. Приведем соответствующие обработчики события Click для каждого из героев игры:

    Private Sub Man_Click()
    	ManInBoat
    	'Call IntoBoat(Me.Man, StateOfMan)
    	
    End Sub
    
    Private Sub Wolf_Click()
    	WolfInBoat
    	'Call IntoBoat(Me.Wolf, StateOfWolf)
    End Sub
    
    Private Sub Goat_Click()
    	GoatInBoat
    	'Call IntoBoat(Me.Goat, StateOfGoat)
    	
    End Sub
    
    Private Sub Cabbage_Click()
    	CabbageInBoat
    	'Call IntoBoat(Me.Cabbage, StateOfCabbage)
    	
    End Sub

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

    Public Sub ManInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfMan = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfMan = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Man.Top = .Boat.Top - 30
    			.Man.Left = .Boat.Left + 25
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub
    
    Public Sub WolfInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfWolf = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfWolf = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Wolf.Top = .Boat.Top - 5
    			.Wolf.Left = .Boat.Left + 50
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub
    
    Public Sub GoatInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfGoat = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfGoat = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Goat.Top = .Boat.Top - 20
    			.Goat.Left = .Boat.Left + 100
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub
    
    Public Sub CabbageInBoat()
    	'Посадка пассажиров в лодку
    	If StateOfCabbage = StateOfBoat Then 'лодка и пассажир на одном берегу
    		StateOfCabbage = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			.Cabbage.Top = .Boat.Top + 5
    			.Cabbage.Left = .Boat.Left + 5
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub

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

    Public Sub IntoBoat(Im As Image, ByRef St As String)
    
     'Посадка пассажиров в лодку
    	If St = StateOfBoat Then 'лодка и пассажир на одном берегу
    		St = "InBoat" 'изменяем состояние
    		With WGCForm
    			'Меняя координаты объекта, перемещаем его в лодку
    			Im.Left = .Boat.Left + 5 + CountInBoat * 50
    			Im.Top = .Boat.Top - CountInBoat * 30
    			'увеличиваем число пассажиров
    			CountInBoat = CountInBoat + 1
    		End With
    		TestingState 'Проверка корректности нового состояния
    	End If
    End Sub

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

    Как посадить пассажира в лодку простым перетаскиванием

    Для ребенка, играющего в эту игру, вряд ли на первых порах будет понятно, почему щелчок по объекту приводит к его перемещению в лодку. Интуитивно, более разумным действием, приводящим к посадке объекта в лодку, было бы прямое перетаскивание объекта в лодку из его текущего положения на берегу. Реализацией такого способа перемещения объекта мы сейчас и займемся. При этом мы не будем тащить сам объект, хотя и это не трудно было бы сделать, а используем технику, основанную на специальном объекте DataObject, входящем в состав библиотеки MSForms. Эта техника хороша тем, что она позволяет опустить перетаскиваемый объект точно в лодку. Всякая попытка опустить объект где-либо в другом месте приведет к неуспеху операции перетаскивания. Момент, когда цель перетаскивания достигнута, и можно отпустить нажатую до этого кнопку мыши, определяется тем, что изменяется внешний вид курсора (появляется значок "+"), что и позволяет точно опустить объект в нужное место. Реализацию этого довольно сложного сп особа перетаскивания обеспечивают обработчики трех событий. Первое из этих событий связано с самим перетаскиваемым объектом. В тот момент, когда над объектом нажимается левая кнопка мыши с целью начать его перетаскивать или копировать в точку назначения, возникает событие "MouseMove". В обработчике этого события и следует создать новый объект класса DataObject, определить некоторые его свойства и вызвать метод StartDrag. Этот метод работает совсем не так, как большинство обычных методов. Его действие оканчивается в тот момент, когда завершится операция перетаскивания. В качестве результата метод возвращает 0, если операция закончилась неуспехом, и ненулевое значение в противном случае. Заметьте, что во время перетаскивания, то есть еще до того, как StartDrag завершит работу, будут возникать другие события и, следовательно, будут работать другие обработчики событий. Два таких события будут возникать, когда перетаскиваемый объект достигает точки назначения, точнее, области назначения. В этот момент у целевого объекта возникает событие BeforeDragOver. Обработчик этого события изменяет внешний вид курсора, что является сигналом достижения цели назначения и позволяет отпустить нажатую кнопку мыши. Обработчик второго события у целевого объекта BeforeDropOrPaste и реализует операцию опускания объекта. Если все завершится благополучно, то успехом заканчивает свою работу и метод StartDrag, которому возвращается управление. Подробнее обо всех деталях работы с этим объектом можно прочитать в моей книге, ссылка на соответствующее место в которой была уже сделана. После всех этих пояснений можно привести и обработчики соответствующих событий, реализующих операции по перетаскиванию объектов в лодку:

    Private Sub Man_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    	
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Man"
    		Effect = MyDataObject.StartDrag
    		If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes8 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub
    Private Sub Wolf_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Wolf"
    		Effect = MyDataObject.StartDrag
    	If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes9 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub
    Private Sub Goat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Goat"
    		Effect = MyDataObject.StartDrag
    		If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes10 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub
    Private Sub Cabbage_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
    	Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Cabbage"
    		Effect = MyDataObject.StartDrag
    		If Effect = 0 Then 'перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes11 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub

    Заметьте, для четырех перемещаемых объектов: человека, волка, козы и капусты написаны четыре обработчика события MouseMove. Каждый из них создает свой объект DataObject, запоминает в его свойстве Text название перемещаемого объекта и запускает метод StartDrag. Но цель у всех этих объектов одна - лодка. У объекта Boat два обработчика событий, вне зависимости от числа пассажиров лодки. Обработчик события BeforeDragOver для всех перемещаемых объектов один и тот же, поскольку его задача изменить вид курсора при попадании перемещаемого объекта в область назначения. Обработчик события BeforeDropOrPaste более сложный. Он должен произвести разбор случаев и определить, какой именно объект прибыл в точку назначения и соответствующим образом расположить его в лодке. Анализ свойства Text, объекта DataObject, переданного в качестве параметра обработчику событий, позволяет провести разбор случаев. После этих предварительных замечаний приведем тексты самих обработчиков:

    Private Sub Boat_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора.
    	Cancel = True
    	Effect = fmDropEffectCopy
    End Sub
    
    Private Sub Boat_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения.
    	Cancel = True
    	Effect = fmDropEffectCopy
    	'Разбор случае. Анализ прибывшего объекта и вызов процедуры его размещения в лодке
    	If Data.GetText = "Man" Then
    		ManInBoat
    	ElseIf Data.GetText = "Wolf" Then
    		WolfInBoat
    	ElseIf Data.GetText = "Goat" Then
    		GoatInBoat
    	Else: CabbageInBoat
    	End If
    End Sub

    На этом закончим рассмотрение задачи посадки пассажиров в лодку и перейдем к рассмотрению следующей задачи.

    Переправа. Берег Левый - Берег Правый

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

    Private Sub Boat_Click()
    	'Переправа на другой берег
    	Crossing
    End Sub
    
    Public Sub Crossing()
    	'Переправа на другой берег
    	'Есть ли человек в лодке
    	With WGCForm
    	 
    		If StateOfMan = "InBoat" Then
    			If StateOfBoat = "LeftBank" Then
    				'Едем на правый берег
    				'Меняем координаты лодки и кормчего
    				StateOfBoat = "RightBank"
    				.Boat.Left = .Boat.Left + WidthOfRiver
    				.Man.Left = .Man.Left + WidthOfRiver
    				'Анализ присутствующих пассажиров
    				If StateOfWolf = "InBoat" Then
    					.Wolf.Left = .Wolf.Left + WidthOfRiver
    				End If
    				If StateOfGoat = "InBoat" Then
    					.Goat.Left = .Goat.Left + WidthOfRiver
    				End If
    				If StateOfCabbage = "InBoat" Then
    					.Cabbage.Left = .Cabbage.Left + WidthOfRiver
    				End If
    			Else
    				'Едем на левый берег
    				 'Меняем координаты лодки и кормчего
    				 StateOfBoat = "LeftBank"
    				.Boat.Left = .Boat.Left - WidthOfRiver
    				.Man.Left = .Man.Left - WidthOfRiver
    				'Анализ присутствующих пассажиров
    				If StateOfWolf = "InBoat" Then
    					.Wolf.Left = .Wolf.Left - WidthOfRiver
    				End If
    				If StateOfGoat = "InBoat" Then
    					.Goat.Left = .Goat.Left - WidthOfRiver
    				End If
    				If StateOfCabbage = "InBoat" Then
    					.Cabbage.Left = .Cabbage.Left - WidthOfRiver
    				End If
    			End If
    		End If
    		
    	End With
    End Sub

    Более интересна техника перетаскивания лодки с берега на берега. Ранее лодка служила целью назначения перетаскиваемых в нее объектов. Теперь она сама станет перемещаемым объектом и, следовательно, для нее мы создадим обработчик события MouseMove, в котором и вызовем метод StartDrag объекта DataObject. В роли целевых будут выступать в зависимости от ситуации объекты LeftBank и RightBank, для каждого из которых будут написаны по два обработчика событий BeforeDragOver и BeforeDropOrPaste. О них позже пойдет более подробный разговор, а сейчас для лодки приведем текст обработчика события MouseMove, запускающего процесс перемещения:

    Private Sub Boat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
     Dim Effect As Integer
    	'MyDataObject используется при перетаскивании объектов
    	Dim MyDataObject As DataObject
    
    	If Button = 1 Then
    		Set MyDataObject = New DataObject
    		MyDataObject.SetText "Boat" 'Запоминаем имя перемещаемого объекта
    		Effect = MyDataObject.StartDrag 'запускаем процесс перемещения
    		If Effect = 0 Then перетаскиваемый объект не достиг цели
    			'Сообщение о неуспехе
    			MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes15 _
    			+ vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		End If
    	End If
    End Sub

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

    Высадка пассажиров из лодки на берег

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

    Private Sub LeftBank_Click()
    	'Все пассажиры из лодки высаживаются на левый берег
    	ONLeftBank ("All")
    End Sub
    Private Sub RightBank_Click()
    	'Все пассажиры из лодки высаживаются на правый берег
    	OnRightBank ("All")
    End Sub

    Сами обработчики являются простыми, поскольку вся обработка спрятана в вызываемых процедурах стандартного модуля, которым в качестве параметра передается слово "All", указывающее на необходимость высадки на берег всех пассажиров. Эта же процедура будет использоваться и во второй стратегии, когда пассажиры высаживаются по одному. Давайте перейдем к ее рассмотрению. Для самих перетаскиваемых объектов обработчики события MouseMove, запускающие перетаскивание объекта, уже написаны. Мы рассматривали их, когда речь шла о перемещении объектов с берега в лодку. Теперь изменилась цель перетаскивания из лодки на берег, левый или правый в зависимости от того, куда причалила лодка. Поэтому нам надо лишь рассмотреть обработчики событий для целевых объектов LeftBank и RightBank. Напомним, эти объекты являются целевыми и при причаливании лодки к берегу. Вот тексты этих обработчиков:

    Private Sub LeftBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора.
    	Cancel = True
    	Effect = fmDropEffectCopy
    End Sub
    
    Private Sub LeftBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения.
    	Cancel = True
    	Effect = fmDropEffectCopy
    	'Пассажир высаживается на левый берег или лодка причаливает к нему
    	ONLeftBank (Data.GetText)
    End Sub
    Private Sub RightBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора.
    	Cancel = True
    	Effect = fmDropEffectCopy
    
    End Sub
    
    Private Sub RightBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
    	'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения.
    Cancel = True
    	Effect = fmDropEffectCopy
    	'Пассажир высаживается на правый берег или лодка причаливает к нему
    	OnRightBank (Data.GetText)
    End Sub

    Все программистские сложности спрятаны в процедурах стандартного модуля OnLeftBank и OnRightBank. Именно в них предусмотрен разбор случаев, то ли лодка причалила к берегу, то ли человек, то ли какой-либо из его спутников высаживается на берег. Здесь же предусмотрена проверка того, на какой берег в данный момент возможна высадка или причаливание.

    Public Sub ONLeftBank(Who As String)
    	If StateOfBoat = "LeftBank" Then
    	'пассажиров из лодки можно высадить на левый берег
    	With WGCForm
    	If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then
    		'можно высадить человека
    		.Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10
    		StateOfMan = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then
    		'можно высадить волка
    		.Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10
    		StateOfWolf = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then
    		'можно высадить козу
    		.Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10
    		StateOfGoat = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then
    		'можно высадить капусту
    		.Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10
    		StateOfCabbage = "LeftBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	End With
    	ElseIf (Who = "Boat") Then
    		'Лодка переезжает на левый берег
    		Crossing
    	End If
    	TestingState
    End Sub
    
    Public Sub OnRightBank(Who As String)
    	If StateOfBoat = "RightBank" Then
    	'пассажиров из лодки можно высадить на правый берег
    	With WGCForm
    	If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then
    		'можно высадить человека
    		.Man.Top = .RightBank.Top + 15: .Man.Left = .RightBank.Left + 10
    		StateOfMan = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then
    		'можно высадить волка
    		.Wolf.Top = .RightBank.Top + 80: .Wolf.Left = .RightBank.Left + 10
    		StateOfWolf = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then
    		'можно высадить козу
    		.Goat.Top = .RightBank.Top + 140: .Goat.Left = .RightBank.Left + 10
    		StateOfGoat = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then
    		'можно высадить капусту
    		.Cabbage.Top = .RightBank.Top + 200: .Cabbage.Left = .RightBank.Left + 10
    		StateOfCabbage = "RightBank"
    		CountInBoat = CountInBoat - 1
    	End If
    	End With
    	ElseIf (Who = "Boat") Then
    	'Лодка переезжает на правый берег
    	Crossing
    	End If
    	TestingState
    End Sub

    Тестирование состояний и организация диалога

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

    Public Sub TestingState()
    	'Тестирование состояний
    	With WGCForm
    	'Волк съел козу
    	If (StateOfWolf = StateOfGoat) And (StateOfWolf <> StateOfMan) Then
    		.Goat.Visible = False
    		MsgBox Prompt:=Mes1 + vbCrLf + Mes3, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		InitialStates
    	End If
    	'Коза съела капусту
    	If (StateOfCabbage = StateOfGoat) And (StateOfCabbage <> StateOfMan) Then
    		.Cabbage.Visible = False
    		MsgBox Prompt:=Mes1 + vbCrLf + Mes4, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		InitialStates
    	End If
    	'Слишком много пассажиров!
    	If (CountInBoat > 2) Then
    		If StateOfMan = "InBoat" Then .Man.Visible = False
    		If StateOfWolf = "InBoat" Then .Wolf.Visible = False
    		If StateOfGoat = "InBoat" Then .Goat.Visible = False
    		If StateOfCabbage = "InBoat" Then .Cabbage.Visible = False
    		MsgBox Prompt:=Mes1 + vbCrLf + Mes5, Buttons:=vbCritical + vbOKOnly, Title:=Mes13
    		InitialStates
    	End If
    	'Конец игры - Все пассажиры собрались на правом берегу
    	If (StateOfMan = "RightBank") And (StateOfWolf = "RightBank") And _
    		(StateOfGoat = "RightBank") And (StateOfCabbage = "RightBank") Then
    		MsgBox Prompt:=Mes2 + vbCrLf + Mes6, Buttons:=vbExclamation + vbOKOnly, Title:=Mes14
    	End If
    	End With
    End Sub

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

    (рис 6.2) Одна из критических ситуаций в игре "Волк, Коза и Капуста"

    Заметим, что после нажатия кнопки "OK" в окне выдачи сообщения вызывается процедура InitialStates и игра возвращается в начальное состояние. Скажем еще несколько слов об организации диалога. Все константы, используемые в диалоге, собраны в разделе объявлений стандартного модуля WGCModule:

    'Константы для организации диалогов
    Public Const Mes1 = "Ужасно, нелепо и грустно! "
    Public Const Mes2 = "Прекрасно, как славно, отлично! "
    Public Const Mes3 = " Волк съел козу! "
    Public Const Mes4 = " Коза съела капусту! "
    Public Const Mes5 = " Лодка перевернулась, и все утонули! "
    Public Const Mes6 = " Вам это удалось! Все переправились!"
    Public Const Mes7 = " Не удалось дотащить "
    Public Const Mes8 = " Человека! "
    Public Const Mes9 = " Волка! "
    Public Const Mes10 = " Козу! "
    Public Const Mes11 = " Капусту! "
    Public Const Mes12 = " Повторите попытку! "
    Public Const Mes13 = " Беда!"
    Public Const Mes14 = " Радость! "
    Public Const Mes15 = " Лодку! "

    Такой подход позволяет при необходимости легко перейти на другой язык диалога, или сделать выбор языка параметром игры. Разумно также, но я не стал этого делать, собрать все диалоги в специальной процедуре Dialogs. Я отказался также от использования объекта Assistant при ведении диалога, хотя он очень подходит для подобных игр. Последнее решение обусловлено тем, что мне хотелось в чистом виде сохранить концепцию документа - обложки, а объект Assistant является специфическим объектом Office 2000. Его использование не позволило бы перенести игру без всяких хлопот в чистый VB.

    Завершающий шаг

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

    (рис 6.3) Окончание игры "Волк, Коза и Капуста"

    Чтобы начать игру с начала, достаточно щелкнуть по объекту Shark, который до сих пор никак еще не использовался. В ответ на событие Click вызывается процедура InitialStates:

    Private Sub Shark_Click()
    	InitialStates
    End Sub

    Для того чтобы игра начиналась с открытием документа - обложки, зададим обработку события Open для этого документа:

    Private Sub Document_Open()
    	WGCForm.Show
    End Sub

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

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

    Перенос игры в чистый VB

    Я уже говорил, что одно из достоинств документов типа "обложка", что они сравнительно легко переносятся в другие программные среды. Проще всего, программный проект, сделанный на VBA и находящийся под обложкой одного из документов Office 2000, перенести в чистый VB. Для этого не потребуется почти никаких усилий. Этим мы сейчас и займемся. Наш программный проект состоит из одной формы WGCForm и одного стандартного модуля WGCModule. Нет никаких проблем, чтобы экспортировать эту форму и модуль, сохранив их в виде файлов с уточнениями frm и bas соответственно. Для этого достаточно выбрать пункт ExportFile из главного меню File или из контекстного меню, доступного при нажатии правой кнопки на модуле или форме в окне проекта. После того, как форма и модуль сохранены, они могут быть импортированы в любой из документов Office 2000, где начнут работать под другой обложкой. Более интересно, что точно также они могут быть импортированы в чистый VB, что я и сделал. Взгляните, как выглядит проект VB после переноса в него нашей формы и модуля.

    (рис 6.4) Проект WGC, перенесенный в среду VB6

    Следует обратить внимание на то, что формы VB и VBA это все-таки разные формы для VB. Формы VBA загружаются в среде VB специальным загрузчиком таких форм ActiveX Designer MSForms. Этот дизайнер включен во все версии VB6, поэтому на компьютере достаточно иметь VB и нет необходимости в существовании Office 2000. Подобно всем другим дизайнерам VB он имеет собственную динамически подключаемую библиотеку (run time dll). Это приводит к увеличению накладных расходов, но сам перенос осуществляется без проблем. Теперь, имея проект на VB, можно использовать все преимущества этой среды, сохранив наш проект, например, в виде исполняемого exe - файла, в виде DLL или в виде ActiveX Control.

    Следует сделать одно важное замечание, связанное с переносом в VB. Мне все-таки пришлось внести одно изменение в программный текст, прежде, чем проект заработал. Это изменение связано с объектом DataObject. Дело в том, что в VBA - проекте я использовал этот объект, не уточнив его полное имя. По умолчанию предполагалось, что речь идет об объекте DataObject библиотеки MSForms. Когда же проект стал компилироваться в чистом VB, то по умолчанию этот объект стал восприниматься, как объект VB и здесь возникла некоторая путаница. Хотя язык VB позволяет работать с визуальными объектами библиотеки MSForms, он имеет и собственную, весьма похожую библиотеку визуальных объектов. Замечу, что объект DataObject существует в этой библиотеке и, выполняя практически те же функции, является более мощным объектом. Вместо методов GetText и SetText он обладает методами GetData и SetData, способными передавать не только текст, но и данные более сложной структуры, в том числе метафайлы и графические файлы с уточнением bmp. Конеч но, я не стал переписывать текст, работая с новым, более мощным объектом VB. Я всюду в тексте заменил DataObject на полное имя MSForms.DataObject. После чего все заработало, как нужно.

    Несколько слов об обратном переносе программных проектов из VB в VBA. Такой перенос возможен, хотя выполняется сложнее. Прежде всего, замечу, что формы VB не переносятся напрямую в VBA. Для того, чтобы из VB перенести форму, необходимо разрабатывать ее, используя ActiveX Designer MSForms. Ну и, конечно, нужно следить за тем, чтобы реализация не использовала такие объекты VB, для которых нет точных аналогов в VBA, например, не следует в реализации использовать возможности объекта DataObject, принадлежащего чистому VB.

    Создание документов "вручную". Программирование без программирования

    Документы типа "ручная работа"

    Я уже говорил о том, что офисному программисту приходится начинать свою работу над документом не с нуля. Зачастую, документ в значительной своей части создан вручную и обладает широким спектром стандартных возможностей. Задача программиста состоит в том, чтобы придать документу новые возможности, учитывающие специфику решаемых задач. Документы, созданные вручную и не требующие программирования или требующие его лишь в малой степени, я и называю документами типа "ручная работа". Хотя с позиций офисного программирования такие документы представляют крайний, предельный случай и менее интересны, чем "настоящие", "сшитые на заказ" документы, но нужно отчетливо понимать, что документы " ручной работы" широко распространены. Чаще всего такие документы создают сами пользователи, но иногда эту работу необходимо выполнять и программисту. Созданию и работе с такими документами посвящено большое количество книг и статей, адресованных пользователям. Это отдельная и большая тема, выходящая за пределы рассматриваемых мной вопросов, но полностью обойтись без их рассмотрения не удается, поскольку это естественная часть работы офисного программиста. Поэтому я хочу рассмотреть один пример создания документа подобного рода, а главное, поговорить о том, что для таких документов иногда возможно создание программного проекта без явного программирования, используя возможности такого средства как упоминавшийся мной MacroRecorder.

    Создание электронного бланка "СЧЕТ - ФАКТУРА"

    Разработка электронной формы бланка заказа.

    В офисе работают с людьми и с документами. Оставим в стороне людей и займемся документами. В современном офисе документы имеют электронную форму. Давайте рассмотрим все этапы создания документа в электронной форме на примере бланка "СЧЕТ-ФАКТУРА" - типичного документа многих офисов. Будем создавать этот документ вручную, не прибегая к программированию, используя только стандартные возможности Office 2000. Конечно же, используя программирование на VBA, этот документ можно сделать куда более содержательным, например, связав его с базой данных. Но отложим рассмотрение этих вопросов на будущее и сосредоточимся на работе вручную и автоматическому получению макросов, запуск которых будет повторять работу, которую мы будем делать руками.

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

  • Начинайте разработку с создания его эскиза, если только не предусмотрен стандарт на форму этого бланка.
  • Выберите подходящий шаблон. Возможность быстрого создания документов на основе шаблонов - одно из главных преимуществ Office. Если такого шаблона нет, целесообразно его создать и уже на его основе разработать окончательный вид документа.
  • Создать удобный для пользователя документ в электронной форме удается, как правило, лишь с учетом замечаний пользователей, полученных в процессе работы с этим документом. Поэтому начинайте с прототипа. Если подходящий шаблон есть, документ, полученный как его копия, может с успехом служить прототипом.
  • Следуя этим советам, я создал эскиз бланка "СЧЕТ-ФАКТУРА". Конечно, для таких бланков есть зафиксированный стандарт. Я не стал строго следовать этому стандарту, поскольку преследую другие цели. Тем не менее, возможно, что бланк, который будет построен, может послужить шаблоном для создания "настоящего" бланка "СЧЕТ-ФАКТУРА". Наш бланк будет содержать:

  • Шапку, включающую логотип, название и реквизиты офиса (поставщика). Шапка - постоянная часть многих бланков офиса формируется автоматически, не требуя от пользователя, работающего с бланком, каких-либо действий; она может быть общей для многих бланков;
  • Реквизиты покупателя; эта переменная часть бланка заполняется при каждом оформлении заказа.
  • Данные о грузоотправителе и грузополучателе.
  • Таблица заказа - основная часть бланка заказа. Ее поля содержат сведения о заказываемом товаре: название, цену за единицу, количество заказанных единиц, общую стоимость и другие данные.
  • Утверждающие подписи
  • Пожалуй, достаточно. На этих составных частях нашего бланка мы пока и остановимся. С одной стороны, здесь есть все главные части, с другой стороны, подробное обсуждение того, как они создаются, потребует немалого времени. Чтобы легче воспринимать дальнейшее обсуждение, давайте взглянем на эскиз электронного бланка
  • (рис 6.5) Эскиз электронного бланка

    Конечно, внимательный читатель понял, что перед ним не "бумажный" рисунок, а реальный документ, разработанный средствами Office. Но в каком из приложений он создан? Мог ли он догадаться, что перед ним рабочая страница Excel? Аналогичный бланк можно было бы создать и в других приложениях Office таких, как Word и Access. Сейчас я подробно опишу, как был создан в Excel подобный шаблон, отступая, возможно, в некоторых деталях, так что окончательный результат будет немного отличаться от показанного на рисунке.

    Разработка шапки электронного бланка

    Как уже говорилось, шапка бланка будет включать логотип офиса, его название и другие реквизиты. Так как шапка может применяться при создании разных бланков, стоит разработать для нее отдельный шаблон. Более того, используя Macrorecorder, будет создан макрос, строящий эту шапку. Сразу же замечу, что это будет не один макрос, а серия небольших макросов, отражающих отдельные этапы работы. Я полагаю, что это правильная технология использования MacroRecorder. Макросы должны быть небольшими и соответствовать отдельным функциям, которые можно выделить естественным образом. В этом случае набор таких макросов допускает переиспользование и процесс создания нового шаблона во многом может свестись к сборке из набора готовых макросов. Мы пойдем по этой дороге, и созданные макросы помогут нам проследить за каждым шагом работы вручную, даже если мы и не опишем подробно его на словах.

    Выделим следующие этапы в создании шапки:

  • форматирование листа бланка на рабочем листе Excel;
  • рисование графического образа, задающего логотип - торговую марку офиса;
  • оформление названия офиса с помощью WordArt;
  • создание рамки, окаймляющей реквизиты офиса.
  • Форматирование листа бланка.

    Обычный лист Excel содержит "лишние" для бланка детали. Пользователь, работающий с бланком, может и не подозревать, что он работает с рабочим листом Excel, он должен видеть перед собой "обычный" лист с именованными полями, в которые заносится нужная информация. Поэтому я отключил такие элементы рабочего листа, как сетку, выделяющую ячейки, заголовки столбцов и строк, а также строку листа Excel, в которой отображаются имена ячеек и формулы, записанные в них. Это достигается установкой соответствующих переключателей в окне Options (Параметры) из меню Tools (Сервис). Следующим шагом работы является выделение области ячеек, соответствующих размеру электронного бланка и выделение ее подходящим цветом фона. На этом этапе я использовал возможности, предоставляемые для задания фона в окне Patterns (Вид) при выборе команды Cells (Ячейки) из меню Format (Формат). Замечу, что хотя совершенно естественно начинать описание процесса создания бланка с его форматирования, реально форматирование следует выполнять на п оследнем этапе, когда бланк полностью готов и осталось убрать лишние детали.

    Итак, вот моя программа действий по форматированию бланка с записью соответствующего макроса:

  • открыл чистый рабочий лист Excel;
  • вызвал запись макроса, которому дал имя "ФорматированиеБланка ";
  • удалил "лишние" элементы рабочего листа: сетку и заголовки строк и столбцов;
  • выделив область ячеек, соответствующую размеру бланка, залил ее подходящим цветом;
  • закончил запись макроса.
  • Приведу текст полученного макроса с добавленными мной комментариями:

    Sub ФорматированиеБланка()
    '
    ' ФорматированиеБланка Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    ' Этот макрос удаляет сетку, заголовки строк и столбцов, строку формул,
    	'показ нулей в ячейках рабочего листа. Выделяет подходящим фоном рабочую
    	'область бланка. В данном примере: A1:K56
    
    	Range("A1:K56").Select
    	With Selection.Interior
    		.ColorIndex = 15	'серый цвет
    		.Pattern = xlSolid
    		.PatternColorIndex = xlAutomatic
    	End With
    	With ActiveWindow
    		.DisplayGridlines = False	'сетка
    		.DisplayHeadings = False	'заголовки
    		.DisplayFormulas = False	'показ формул
    		.DisplayZeros = False		'отображение нулей
    	End With
    	Application.DisplayStatusBar = False	'строка статуса
    End Sub

    MacroRecorder помещает все создаваемые макросы в отдельный модуль, который я переименовал, назвав его "БланкСчетФактура".

    Создание логотипа

    Каждый приличный офис имеет логотип. Наш офис, который мы будем называть "Родная Речь" такой логотип имеет. Под логотипом мы понимаем графический образ, являющийся символом организации и размещаемый на ее бланках. Допустим, рисунок логотипа уже создан, и нам остается только поместить его в нужном месте бланка, возможно, согласовав размеры. Есть две возможности размещения его на бланке. При создании логотипа офиса "РР" я нашел рисунок бабочки и пририсовал в графическом редакторе на каждом из ее крылышек букву "Р", затем привел рисунок к нужным размерам. Чтобы поместить его на бланк, можно вставить рисунок в ячейку рабочего листа. Для этого нужно выделить подходящую ячейку и указать имя файла в окне, раскрывающемся при выборе команды From File (Из Файла) пункта Picture (Рисунок) меню Insert (Вставить). Но лучше идти более сложным путем: разместить на бланке элемент управления "Image", а затем, используя свойство Picture этого элемента, указать, где хранится рисунок. Несомненным преимуществом такого подхода является возможность использования рисунка произвольного размера. Меняя его свойства, можно его растянуть или сжать до приемлемых размеров. Кроме того, этот элемент управления реагирует на события и можно написать процедуру, которая, например, будет выдавать дополнительную информацию, скажем, о "главных" людях офиса, в тот момент, когда пользователь щелкнет кнопкой по области, занятой логотипом. Обращаем Ваше внимание на то, что при помещении на рабочий лист Excel элемента управления, он вставляется как OLE-объект и становится частью коллекции OLEObjects.

    Я выбрал путь, использующий OLE-объекты. Мои действия записывал макрос, который был назван "Логотип":

    Sub Логотип()
    '
    ' Логотип Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    	'Этот макрос создает элемент управления Image
    	'и помещает его в нужную позицию рабочего листа.
    
    	ActiveSheet.OLEObjects.Add(ClassType:="Forms.Image.1", Link:=False, _
    		DisplayAsIcon:=False, Left:=23.25, Top:=15, Width:=63, Height:=61.5). _
    		Select
    End Sub

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

  • Image получил имя Логотип;
  • включив свойство Picture, в появившемся окне я указал файл, содержащий образ с логотипом (бабочку РР);
  • свойству PictureSizeMode приписал значение 3 (PictureSizeModeZoom), при котором картинка распахивается на все окно, отведенное образу;
  • "Красивое имя"

    Рядом с логотипом поместим название офиса: "Издательство Родная Речь". Чтобы эта надпись выглядела незаурядно, воспользуемся возможностями, которые предоставляет инструментальная панель WordArt для художественного исполнения надписей. При написании слов "Издательство" и "Родная Речь" я использовал разные стили. С объектной точки зрения каждая из этих надписей представляет объект класса Shape. Я визуально менял свойства этих объектов, а Macrorecorder фиксировал мои действия в макросе с именем "КрасивоеИмя". Вот текст этого макроса:

    Sub КрасивоеИмя()
    '
    ' КрасивоеИмя Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    	'Этот макрос включает инструментальную панель WordArt
    	'и с ее помощью создает два рисованных объекта класса Shape
    	'первый из них задает слово "Издательство", второй -"Родная Речь"
    
    	ActiveSheet.Shapes.AddTextEffect(msoTextEffect10, "Издательство", _
    	"Arial Black", 24#, msoFalse, msoFalse, 177.75, 138.75).Select
    	Selection.ShapeRange.IncrementLeft -34.5
    	Selection.ShapeRange.IncrementTop -126#
    	ActiveSheet.Shapes.AddTextEffect(msoTextEffect4, _
    		"Р о д н а я	Р е ч ь ", _
    		"Impact", 20#, msoFalse, msoFalse, 197.25, 138.75).Select
    	Selection.ShapeRange.IncrementLeft -30#
    	Selection.ShapeRange.IncrementTop -80.25
    End Sub

    Формирование рамки с реквизитами офиса

    Заключительный шаг в формировании шапки - заполнение полей с реквизитами офиса: адресом, телефоном и др. Визуально эту операцию выполнить просто. В одной ячейке записывается название реквизита, а в ячейке справа его значение. Обычно одной ячейки для значения не хватает, и требуется расширить это поле до нужных размеров. Чтобы не менять размеры столбцов, проще всего слить идущие подряд ячейки. Вручную для этого нужно выделить ячейки и включить переключатель MergeCells (Объединение ячеек) на вкладке Alignment (Выравнивание) из пункта Cells (Ячейки) меню Format (Формат).

    Чтобы объединить поля с реквизитами и придать им некоторую структуру, их, как правило, заключают в рамку. Подходящую рамку можно выбрать из набора готовых Shape объектов, доступ к которым можно получить, включив инструментальную панель Рисование (Drawing) с элементами рисования; одна из ее кнопок - Автофигуры (AutoShapes) - позволяет добраться до базисных Shape-фигур. Когда рамка накладывается на поля с реквизитами, поля перестают быть видимыми. Рамку следует сделать прозрачной. Для этого нужно, выделив ее, щелкнуть правой кнопкой мыши, из контекстного меню выбрать пункт FormatAuto Shape и в появившемся окне в разделе заполнения (Fill) выбрать из списка значение для цвета NoFill (без заполнения). В этот момент при желании можно изменить стиль начертания границ рамки.

    Я проделал все описанные выше операции и получил законченную шапку, отвечающую нашему эскизу. Макрос "РеквизитыИРамка" позволит проследить за моими действиями последнего этапа - формирования реквизитов офиса. Заметьте, в завершение шапки я подвел черту, подчеркнув сформированную часть яркой линией.

    Sub РеквизитыИРамка()
    '
    ' РеквизитыИРамка Macro
    ' Macro recorded 27.11.1999 by Vladimir Billig
    
    'Именование полей реквизитов
    	Range("D9:E9").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Адрес"
    	Range("D10:E10").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Телефон, Факс"
    	Range("D11:E11").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Email"
    	Range("D12:E12").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "ИНН"
    	
    	'Запись реквизитов
    	Range("F9:K9").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Москва, ул. Филевская, 15"
    	Range("F10:J10").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "175-3434, 175-3480"
    	Range("F11:J11").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "rr@red.ru"
    	Range("F12:J12").Select
    	Selection.MergeCells = True
    	Selection.HorizontalAlignment = xlLeft
    	ActiveCell.FormulaR1C1 = "198712345"
    	Range("F9:J12").Select
    	Selection.Font.Italic = True
    	
    	'Построение рамки
    	ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
    		105#, 96#, 369.75, 63.75).Select
    	Selection.ShapeRange.Fill.Visible = msoFalse
    	
    	'Линия отчеркивания
    	ActiveSheet.Shapes.AddLine(48.75, 189#, 475.5, 189#).Select
    	Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
    	Selection.ShapeRange.Line.Visible = msoTrue
    	Selection.ShapeRange.Line.Style = msoLineThinThin
    	Selection.ShapeRange.Line.Weight = 3#
    End Sub

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

    Макрос "Шапка"

    Мы выполнили всю работу по созданию шапки, разделив ее на четыре этапа. На каждом из этих этапов Macrorecorder создал соответствующий макрос. Осталось объединить эти макросы в один, - мы назвали его "Шапка". Он очень прост: последовательно вызывает уже созданные макросы каждого шага:

    Sub Шапка()
    	'Объединение четырех макросов для построения шапки бланка
    	ФорматированиеБланка
    	Логотип
    	КрасивоеИмя
    	РеквизитыИРамка
    End Sub

    Запустив этот макрос на чистом листе, мы получим заполненную шапку. Точнее, почти заполненную, поскольку макрос "Логотип" не записал действий с объектом класса Image по изменению его свойств. Так что нужно приложить некоторые усилия и повторить действия по установлению свойств объекта, чтобы увидеть картинку, связанную с объектом Image. Взгляните на результат работы макроса "Шапка":

    (рис 6.6) Построение шапки

    Реквизиты покупателя

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

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

    Sub РеквизитыЗаказчика()
    '
    ' РеквизитыЗаказчика Macro
    ' Macro recorded 28.11.1999 by Vladimir Billig
    
    '	Именование бланка
    	Range("C16:I16").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "СЧЕТ-ФАКТУРА №					от "
    	With ActiveCell.Characters(Start:=1, Length:=31).Font
    		.FontStyle = "Полужирный"
    		.Size = 14
    	End With
    	With ActiveCell.Characters(Start:=32).Font
    		.FontStyle = "Полужирный Курсив"
    		.Size = 11
    	End With
    	'Задание полей реквизитов заказчика
    	Range("B19:C19").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Покупатель"
    	Range("B20:C20").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Адрес"
    	Range("B21:C21").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Тел., Факс, Email"
    	Range("B22:C22").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "ИНН"
    	Range("D19:J19").Select
    	Selection.MergeCells = True
    	Range("D20:J20").Select
    	Selection.MergeCells = True
    	Range("D21:J21").Select
    	Selection.MergeCells = True
    	Range("D22:J22").Select
    	Selection.MergeCells = True
    	'Создание рамки
    	ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
    		34.5, 231.75, 452.25, 63.75).Select
    	Selection.ShapeRange.Fill.Visible = msoFalse
    	'Надпись на рамке
    	ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, _
    		95, 225, 60, 15).Select
    	Selection.Characters.Text = "Покупатель"
    	
    	'Реквизиты грузоотправителя и грузополучателя
    	Range("B25:D25").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Грузоотправитель и его адрес"
    	Range("E25:J25").Select
    	Selection.MergeCells = True
    	Range("B26:D26").Select
    	Selection.MergeCells = True
    	ActiveCell.FormulaR1C1 = "Грузополучатель и его адрес"
    	Range("E26:J26").Select
    	Selection.MergeCells = True
    	Range("B25:J26").Select
    	Selection.Font.FontStyle = "Полужирный Курсив"
    	Selection.Font.Size = 9
    	'Отчеркивание
    	ActiveSheet.Shapes.AddLine(44.25, 357.75, 500.25, 357.75).Select
    	Selection.ShapeRange.Line.Style = msoLineThinThin
    	Selection.ShapeRange.Line.Weight = 3#
    	Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
    	Selection.ShapeRange.Line.Visible = msoTrue
    End Sub

    Запустив макрос "РеквизитыЗаказчика", на рабочем листе с уже созданной шапкой я получил следующую часть бланка:

    (рис 6.7) Шапка и раздел с реквизитами заказчика

    Раздел "Таблица заказа"

    Основная часть этого бланка - таблица со сведениями о заказываемых товарах. Сетка, которая обычно очерчивает границы ячеек рабочего листа, была удалена, на электронном бланке заказа она неуместна. Теперь необходимо восстановить некоторые из этих границ, чтобы нарисовать таблицу в привычной для глаз форме. На этом этапе я буду работать с вкладкой Borders ("Границы"), открываемой в окне Format Cells ("Формат ячеек") из меню Format ("Формат"). С объектной точки зрения границы объекта класса Range составляют коллекцию Borders. Меняя свойства элементов этой коллекции (объектов Border), можно добиться нужного эффекта. Я построю таблицу в три этапа:

  • столбцы таблицы;
  • шапку таблицы с заголовками полей;
  • последнюю, итоговую строку.
  • Такое разделение сделает обозримыми макросы, транслирующие мои действия в тексты на VBA.

    Построение столбцов

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

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

    Sub СтолбцыТаблицы()
    '
    ' СтолбцыТаблицы Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    
     'Изменение ширины полей, меняя размеры столбцов
    	Columns("E:E").ColumnWidth = 4
    	Columns("F:F").ColumnWidth = 4
    	Columns("I:I").ColumnWidth = 4.43
    	Columns("K:K").ColumnWidth = 11.86
    
    	'Слияние ячеек для поля Название Товара
    	Range("A32:D32").Select
    	Selection.MergeCells = True
    	'Выделение внешних границ: слева,снизу, справа
    	Range("A32:K32").Select
    	With Selection.Borders(xlEdgeLeft)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	With Selection.Borders(xlEdgeBottom)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	With Selection.Borders(xlEdgeRight)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	'Выделение вертикальной внутренней границы
    	With Selection.Borders(xlInsideVertical)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	'Копирование формата на всю область таблицы
    	 Selection.Copy
    	Range("A33:K46").Select
    	Selection.PasteSpecial Paste:=xlFormats, Operation:=xlNone, _
    		SkipBlanks:=False, Transpose:=False
    	Application.CutCopyMode = False
    End Sub

    Шапка таблицы

    Шапка таблицы будет состоять из двух строк, в первой содержатся названия полей, во второй их индексы. Используя соответствующие атрибуты на вкладке Alignment (Выравнивание), я задал центрирование текста по вертикали и горизонтали, а также автоматическое изменение высоты строки, чтобы текст названия полей был полностью видимым. Кроме того, я выделил графически границы шапки, задав их двойными линиями. Макрос получается, конечно, большим, поскольку оперирует с большим числом объектов. Вот его текст:

    Sub ШапкаТаблицы()
    '
    ' ШапкаТаблицы Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    
    	'Именование полей таблицы и задание индексов
    	Range("A32:D32").Select
    	ActiveCell.FormulaR1C1 = "Наименование товара"
    	Range("E32").Select
    	ActiveCell.FormulaR1C1 = "Единица измерения"
    	Range("F32").Select
    	ActiveCell.FormulaR1C1 = "Количество"
    	Range("G32").Select
    	ActiveCell.FormulaR1C1 = "Цена"
    	Range("H32").Select
    	ActiveCell.FormulaR1C1 = "Сумма"
    	Range("I32").Select
    	ActiveCell.FormulaR1C1 = "Ставка НДС"
    	Range("J32").Select
    	ActiveCell.FormulaR1C1 = "Сумма НДС"
    	Range("K32").Select
    	ActiveCell.FormulaR1C1 = "Всего с НДС"
    	Range("A33:D33").Select
    	ActiveCell.FormulaR1C1 = "1"
    	Range("E33").Select
    	ActiveCell.FormulaR1C1 = "2"
    	Range("F33").Select
    	ActiveCell.FormulaR1C1 = "3"
    	Range("G33").Select
    	ActiveCell.FormulaR1C1 = "4"
    	Range("H33").Select
    	ActiveCell.FormulaR1C1 = "5"
    	Range("I33").Select
    	ActiveCell.FormulaR1C1 = "6"
    	Range("J33").Select
    	ActiveCell.FormulaR1C1 = "7"
    	Range("K33").Select
    	ActiveCell.FormulaR1C1 = "8"
    	
    	'Центрирование текста и изменение высоты строки,
    	 'обеспечивающее видимость текста
    	Range("A32:K33").Select
    	With Selection
    		.HorizontalAlignment = xlCenter
    		.VerticalAlignment = xlCenter
    		.WrapText = True
    	End With
    	
    	'Выделение границ шапки
    	With Selection.Borders(xlEdgeLeft)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeTop)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeBottom)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeRight)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlInsideVertical)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    	
    	
    End Sub

    Последняя строка

    Последняя строка, как и строки шапки, отличается от остальных строк таблицы. Она используется для подведения итогов. Также как и шапку, я выделил ее границы графически. Итак, макрос "ПоследняяСтрока":

    Sub ПоследняяСтрока()
    '
    ' ПоследняяСтрока Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    
    	'Задание итоговой строки
    	Range("A46:D46").Select
    	ActiveCell.FormulaR1C1 = "Всего к оплате"
    	Range("A46:K46").Select
    	With Selection.Font
    		.Name = "Arial"
    		.FontStyle = "Полужирный"
    		.Size = 11
    	End With
    	
    	'Выделение границ итоговой строки
    	With Selection.Borders(xlEdgeLeft)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeTop)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeBottom)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlEdgeRight)
    		.LineStyle = xlDouble
    		.Weight = xlThick
    	End With
    	With Selection.Borders(xlInsideVertical)
    		.LineStyle = xlContinuous
    		.Weight = xlThin
    	End With
    End Sub

    Задание расчетных формул и форматирование полей

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

    Sub Расчеты()
    '
    ' Расчеты Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    
    	'Форматирование полей и задание расчетных формул
    	'Форматирование поля "Название товара"
    	Range("A34:D45").Select
    	Selection.ShrinkToFit = True
    	
    	'Форматирование поля "Единица Измерения"
    	Range("E34:E45").Select
    	Selection.HorizontalAlignment = xlCenter
    	'Форматирование поля "Цена"
    	Range("G34:G45").Select
    	Selection.NumberFormat = "0.00"
    	'Форматирование поля "Сумма"
    	Range("H34:H46").Select
    	Selection.NumberFormat = "0.00"
    	'Формула: Сумма = Цена * Количество
    	Range("H34").Select
    	ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
    	'Копирование формулы
    	Range("H34").Select
    	Selection.AutoFill Destination:=Range("H34:H45"), Type:=xlFillDefault
    	Range("H34:H45").Select
    	'Итоговая сумма
    	Range("H46").Select
    	ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
    	
    	'Форматирование поля "НДС"
    	Range("I34:I45").Select
    	Selection.NumberFormat = "0%"
    	
    	'Форматирование поля "Сумма НДС"
    	Range("J34:J46").Select
    	Selection.NumberFormat = "0.00"
    	'Формула: Сумма НДС = Сумма * НДС
    	Range("J34").Select
    	ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
    	'Копирование формулы
    	Range("J34").Select
    	Selection.AutoFill Destination:=Range("J34:J45"), Type:=xlFillDefault
    	Range("J34:J45").Select
    	'Итоговая сумма
    	Range("J46").Select
    	ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
    	
    	'Форматирование поля "Всего с НДС"
    	Range("K34:K46").Select
    	Selection.NumberFormat = "0.00"
    	'Формула: Всего с НДС = Сумма + Сумма НДС
    	Range("K34").Select
    	ActiveCell.FormulaR1C1 = "=RC[-3]+RC[-1]"
    	'Копирование формулы
    	Range("K34").Select
    	Selection.AutoFill Destination:=Range("K34:K45"), Type:=xlFillDefault
    	Range("K34:K45").Select
    	'Итоговая сумма
    	Range("K46").Select
    	ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
    	Range("K47").Select
    	
    	'Нулевые значения в таблице не отображаются
    	ActiveWindow.DisplayZeros = False
    End Sub

    Я собрал макросы, строящие отдельные части таблицы под одной обложкой в макросе "ТаблицаРасчеты":

    Sub ТаблицаРасчеты()
      'Построение таблицы
      СтолбцыТаблицы
      ШапкаТаблицы
      ПоследняяСтрока
      Расчеты
    End Sub

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

    (рис 6.8) Таблица продажи товаров бланка Счет-Фактура

    Заключительный макрос "УтверждающиеПодписи"

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

    Sub УтверждающиеПодписи()
    '
    ' УтверждающиеПодписи Macro
    ' Macro recorded 29.11.1999 by Vladimir Billig
    '
    	Range("B50:G50").Select
    	Selection.MergeCells = True
    	With Selection.Font
    		.Name = "Arial"
    		.FontStyle = "Полужирный Курсив"
    		.Size = 11
    	End With
    	ActiveCell.FormulaR1C1 = "Ген. Директор _________________________"
    	
    	Range("B52:G52").Select
    	Selection.MergeCells = True
    	With Selection.Font
    		.Name = "Arial"
    		.FontStyle = "Полужирный Курсив"
    		.Size = 11
    	End With
    	Range("B52:G52").Select
    	ActiveCell.FormulaR1C1 = "Гл. Бухгалтер __________________________"
    	Range("B55").Select
    	ActiveCell.FormulaR1C1 = "М. П."
    End Sub
    Сборка макросов. Макрос "СчетФактура"
    Для завершения работы и получения макроса, который строит шаблон бланка "Счет-Фактура", осталось собрать все макросы, строящие отдельные части бланка. Вот текст заключительного макроса:
    Sub СчетФактура()
    	'Этот заключительный макрос строит шаблон бланка Счет-Фактура
    	'Он вызывает макросы, строящие отдельный части этого бланка
    	Шапка
    	РеквизитыЗаказчика
    	ТаблицаРасчеты
    	УтверждающиеПодписи
    End Sub

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

    Шаблон бланка построен. Что дальше?

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

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

    При создании в Excel бланка "Счет-Фактура" MacroRecorder позволил получил макросы, записавшие практически все действия пользователя, создававшего этот бланк вручную. Единственное, что не позволил MacroRecorder, это проследить за действиями при работе с элементом управления, помещенным на бланк. Я уже говорил о том, что программисту полезно обращаться к MacroRecorder не только для того, чтобы получить соответствующий макрос, который он, возможно, использует в дальнейшем, чтобы не повторять действия вручную. Часто он обращается к MacroRecorder в процессе обучения работе с объектами Office 2000. В тех случаях, когда программист не знает с каким объектом ему лучше работать при решении той или иной задачи, какими свойствами и методами объекта следует пользоваться, то часто полезно обратиться к MacroRecorder, записать макрос и посмотреть, как MacroRecorder решает данную задачу. Например, задачей программиста является создание системы, позволяющей проводить оптимизацию инвестиций. Чтобы понять, как и с какими объектами Excel следует работать в данной ситуации, рекомендуется решить вручную несколько оптимизационных задач, записав при этом соответствующие макросы.

    (рис 6.9) Построение таблицы в документе Word

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

    Не стоит особенно печалиться по поводу сравнительно небольших возможностей MacroRecorder. Это лишь один из вспомогательных инструментов, используемых в офисном программировании. Главное, что есть в распоряжении программиста, работающего в среде Office 2000, - язык VBA, набор объектов из библиотек Office 2000, возможность использовать компоненты, созданные на других языках и в других программных средах, используя все эти средства можно решить практически все задачи, возникающие в жизни офиса при работе с его документами. На этом я и закончу введение в офисное программирование.

    Вернуться к учебному плану