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

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

Автокоррекция (AutoCorrect)

Объект AutoCorrect поддерживает работу по автоматической коррекции набираемых текстов. Я широко использую возможности автокоррекции в своей работе. Чтобы не переключаться с русского на английский при печатании различных терминов, я ввел таблицу соответствующих замен. Теперь я печатаю "Ап", а получаю Application, печатаю "Во", а получаю Word и так далее. Возможности объекта AutoCorrect во многом совпадают с возможностями команды AutoCorrect (Автозамена) меню Tools. В следующем примере включаются все флажки, регулирующие процесс замены, и в коллекцию Entries, вложенную в объект, добавляются два элемента. Каждый элемент задается парой строк, первая из которых задает исходную строку, а вторая строку, заменяющую ее при автокоррекции печатаемого текста:
Public Sub WorkWithAutoCorrect() 'Работа с объектом AutoCorrect 'Включаются все флажки With AutoCorrect .CorrectInitialCaps = True .CorrectSentenceCaps = True .CorrectDays = True .CorrectCapsLock = True .ReplaceText = True .ReplaceTextFromSpellingChecker = True .CorrectKeyboardSetting = True
'В коллекцию Entries, задающую замены при автокоррекции, 'добавляются два элемента. .Entries.AddName:="ДЕ", Value:="Диаграмма Excel" .Entries.AddName:="ГЕ", Value:="График Excel"
End With End Sub
Листинг 1.9.
(html, txt)

Автомакросы

Автомакросы (Auto Macros) - это макросы со специально фиксированными именами. Они вызываются при возникновении ряда событий и являются альтернативным способом их обработки. Можно задействовать как обработчики событий, так и макросы, или оба механизма вместе. Вот макросы, автоматически запускаемые при возникновении соответствующего события в Word:
  • AutoExec - при запуске приложения Word;
  • AutoNew - при создании нового документа;
  • AutoOpen - при открытии существующего документа
  • AutoClose - при закрытии документа;
  • AutoExit - при выходе из приложения

  • Как и обработчики, макросы можно поместить непосредственно в документ или шаблон, на основе которого документ создается. Макрос будет выполняться, если он находится в активном документе, или в шаблоне Normal или в шаблоне, на основе которого открыт активный документ.
    Давайте напишем макрос AutoOpen и поместим его в наш документ. Макрос будет выполнять действия процедуры OnEvents, связывая объект App1 класса EventsOfApp с текущим приложением, что позволит реагировать на события объекту Application. Текст его прост:
    Sub AutoOpen() 'Связывание объекта Application с событиями Set App1.AppEv = Application End Sub
    Листинг 1.70.
    (html, txt)
    Важно, что это все действительно работает, в чем мы убедились, экспериментируя с нашим тестовым документом.

    Автозаголовки (AutoCaptions)

    Коллекция объектов AutoCaptions (AutoCaption) представляет заголовки, которые могут быть автоматически добавлены при вставке в документ OLE-объектов. В эту коллекцию, конечно же, ни добавлять, ни удалять элементы не разрешается, но можно включить или отключить автоматическое добавление заголовка при вставке того или иного OLE-объекта. Работа вручную идет в уже упомянутом диалоговом окне, открываемом по команде Caption. Программно это можно делать так:
    Public Sub >WorkWithAutoLabels() 'Работа с коллекцией автозаголовков Dim Item As AutoCaption
    Debug.Print AutoCaptions.Count For Each Item In AutoCaptions 'Включение автоматической вставки заголовка item.AutoInsert = True Debug.Print Item .Name Next Item
    End Sub
    Листинг 1.7.
    (html, txt)
    Процедура WorkWithAutoLabels включает все автозаголовки и распечатывает их имена. Приведем первую семерку имен OLE-объектов из 28 объектов, которые можно добавить в текущий момент у меня на компьютере:
    Листинг 1.8.
    (html, txt)

    Диалоги с пользователем. Коллекция Dialogs

    Коллекция объектов Dialogs задает совокупность диалоговых окон, встроенных в Word. Добавлять новые или удалять элементы этой коллекции программным путем нельзя. Но соответствующие окна можно открыть и показать на экране дисплея и тем самым организовать диалог с пользователем по теме, заданной соответствующим окном. Коллекция Dialogs и объекты Dialog не принадлежат к общим объектам Office 2000. Вместе с тем в каждом из приложений Office 2000 есть свои объекты Dialogs и Dialog. В совокупности они составляют семейство схожих объектов, имея много общего в своем поведении и некоторую специфику, зависящую от приложения. Есть смысл хотя бы в одном приложении подробно разобраться с этими объектами, тогда в других приложениях можно будет ссылаться на знакомое поведение этих объектов.
    Коллекция Dialogs устроена совсем просто. У нее стандартный для коллекций набор свойств - Count, Application, Creator, Parent и всего лишь один метод Item . Простота устройства коллекции понятна, поскольку ни добавить, ни убавить ничего нельзя, - все элементы встроены. Но, учтите, элементов у этой коллекции, то есть встроенных диалоговых окон достаточно много - более 200. К каждому из элементов коллекции можно добраться, зная его тип, задаваемый соответствующей константой, входящей в перечисление wdWordDialog.
    Объект Dialog устроен более сложно. У него есть шесть свойств - Application, Creator, Parent, CommandName, Type, DefaultTab и четыре метода - Display, Execute, Show и Update. На четырех первых свойствах особо останавливаться не буду, - первые три стандартны, а четвертое, возвращающее имя процедуры, отображающей диалоговое окно, применяется разве что из любопытства. Свойство Type, имеющее статус "только для чтения", возвращает тип окна, - соответствующую константу из перечисления wdWordDialog. Более важным является свойство DefaultTab. Дело в том, что многие из открываемых окон устроены сложно и имеют различные вкладки. Если, используя это свойство, перед открытием окна установить нужную вкладку, то перед пользователем окно откроется на этой вкладке, что облегчит нелегкую жизнь пользователя. Каждая из возможных вкладок задается соответствующей константой, принадлежащей перечислению wdWordDialogTab.
    Поговорим теперь о четырех методах объекта Dialog:
  • Show- открывает диалоговое окно, заданное объектом Dialog, после чего прерывает свою работу, давая возможность пользователю задать нужные установки в диалоговом окне и выполнить некоторые действия, для чего ему достаточно нажать ту или иную кнопку в диалоговом окне. Действия, предписанные пользователем, выполняются, и метод Show заканчивает свою работу, возвращая в качестве результата номер кнопки, нажатой пользователем. Значение - 2 соответствует кнопке Close, -1 - OK, 0 - Cancel, N>0 - командной кнопке с номером N. Вместо стандартных кнопок OK, Close, Cancel могут применяться эквивалентные по сути кнопки с другими названиями, например, "Открыть" вместо кнопки OK.
  • Update - обновляет установки, принятые по умолчанию для диалогового окна. Чаще всего вызывается в самом начале работы с диалоговым окном, перед тем как задаются новые значения установок.
  • Display - альтернатива методу Show. Также как и метод Show, открывает диалоговое окно и позволяет пользователю задать нужные установки и нажать кнопку. Однако не выполняет действия, предписанные пользователем, возвращая, по-прежнему, в качестве результата номер нажатой кнопки. После чего программа может проанализировать действия пользователя и принять окончательное решение, например, принять установки, вызвав метод Execute.
  • Execute - применяет текущие установки диалогового окна.


  • Шесть свойств и 4 метода объекта Dialog - это лишь видимая часть айсберга, каковым является объект Dialog. Нужно понимать, что каждое диалоговое окно, то есть конкретный объект Dialog имеет множество дополнительных параметров (свойств), определяющих специфику этого объекта. Основные сложности работы с этим объектом связаны с пониманием сути параметров и установкой корректных их значений. Реально здесь могут возникать различные проблемы, о которых поговорим чуть позже при рассмотрении одного из примеров. Хочу еще несколько слов сказать об использовании этих объектов. Работа с ними предполагает, что в какой-то момент программа начинает диалог с пользователем на стандартную тему, для которой в системе Office 2000 заготовлено соответствующее окно. Часто, разумно при первом открытии окна дать возможность пользователю сделать свой выбор и установить подходящие значения параметров окна. Затем установки пользователя могут быть сохранены в объекте Dialog. При повторном открытии окна используется этот объект, и окно открывается с установками, повторяющими предыдущий выбор пользователя. К сожалению, сохранение всех установок не всегда возможно.

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

    Public Sub WindowOpenFile() 'Предоставление пользователю возможности открыть любой файл. 'Открывается каталог, хранящий активный документ, с показом всех файлов. Dim Act As Long With Dialogs (wdDialogFileOpen) .Name = ActiveDocument.Path & "\*.*" Act = . Show Debug.Print Act If Act = -1 Then 'Файл успешно открыт и можно получить путь к нему и его имя Debug.Print Documents(1).Path & "\" & Documents(1).Name End If End With

    End Sub

    Листинг 1.11.

    (html, txt)

    Заметьте, открывающееся перед пользователем стандартное окно открытия файла предварительно настроено, так что пользователь сможет увидеть все файлы каталога, хранящего активный документ. Для этой настройки использовалось свойство (параметр) Name, которым обладает данный конкретный объект FileOpen коллекции Dialogs . Замечу, что у этого объекта более 10 различных параметров, с их помощью можно задавать пароли, статус открытия и другие свойства, связанные с открытием файла. В справочной системе в разделе "Built-in Dialog box argument lists" для каждого диалогового окна указан список его возможных параметров. К сожалению, нельзя найти ни описания сути этих параметров, ни их возможных значений. Обо всем приходится догадываться, зная имя параметра и анализируя работу руками в диалоговом окне. Заметьте, контекстные справки к элементам интерфейса диалоговых окон также не всегда существуют.

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

    Давайте рассмотрим еще один, более сложный пример, в котором возникают некоторые проблемы. Рассказывая об общих объектах, я рассмотрел объект Filesearch, используемый для поиска файлов. Теперь рассмотрим, решение этой задачи с использованием соответствующего объекта Dialog. Я напомню, что для поиска файлов в Office 2000 следует открывать не специальное окно поиска файлов, а обычное окно открытия файлов, - то есть выполнить команду "Open". Из этого окна по нажатию кнопки "Сервис" можно перейти в окно поиска и там уже можно сформировать достаточно сложное логическое выражение, задающее условие поиска. Условия поиска могут включать размер файла, дату его создания и внесения изменений, полное или частично заданное имя файла и множество других критериев, включая, в том числе, и полно текстовый поиск. Программная работа с объектом Filesearch позволяет выполнить аналогичные операции. При работе с объектом Dialog большая часть работы возлагается на пользователя, который должен сформировать критерии поиска в момент открытия окна. Программно можно предварительно сформировать лишь некоторые критерии поиска.

    У диалогового объекта Dialog FileFind большое число разнообразных параметров. Не поленюсь и приведу список всех параметров этого объекта, разделив их на две группы. В первую группу входят параметры:SearchPath,Name,SubDir,Title, Author, KeyWords, Subject, MatchCase, Text, PatternMatch, DateSavedFrom, DateSavedTo, SavedBy, DateCreatedFrom, DateCreatedTo. К этой группе я отнес параметры, смысл и значения которых мне понятны, - они служат для формирования соответствующих критериев поиска. Заметьте, что среди них нет многих параметров, доступных при формировании критериев поиска вручную, например, нельзя задать ограничение на размер отыскиваемого файла. Во вторую группу я отнес параметры, смысл или множество возможных значений которых мне не совсем понятны. Вот эти параметры: SearchName, Options, View, SortBy, ListBy, SelectedFile, Add, Delete, ShowFolders, MatchByte.

    От сетований давайте перейдем к примеру работы с этим диалоговым окном:


    Листинг 1.12.

    (html, txt)

    Обратите внимание, перед тем как открыть диалоговое окно методом Show, я программно формирую ряд критериев поиска:

  • Свойство SearchName позволяет указать (я так предполагаю), что поиск ведется среди документов Word.
  • СвойствоName говорит о том, что ищется файл, в имени которого есть сочетание "Ch6".
  • СвойствоSearchPath задает каталог поиска.
  • СвойстваSubDir и MatchCase включают флажки, указывающие, что поиск должен вестись в глубину с просмотром подкаталогов, а при полно текстовом поиске следует проверять словоформы.
  • Свойство DateCreatedFrom указывает, что разыскиваются файлы, созданные не ранее 20 мая 2000 года.
  • Свойство Author задает автора документов.
  • Свойство Text задает слово, чьи словоформы будут искаться в файлах.


  • Выполнение метода Execute непосредственно перед открытием окна позволяет передать в окно эти установки и когда окно будет открыто, то в нем будут отображаться результаты поиска, не требуя никаких действий от пользователя. Вот как выглядит диалоговое окно в момент его открытия методом Show:

    Диалоги с пользователем. Коллекция Dialogs

    увеличить изображение
    Рис. 1.5.  Диалоговое окно поиска файлов в момент открытия

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

    Диалоги с пользователем. Коллекция Dialogs

    Рис. 1.6.  Окно формирования критериев поиска

    Правда, не все параметры, заданные в программе, были успешно переданы в окно. Это касается первых двух параметров - SearchName и Name. Что касается первого из них, то при открытии диалогового окна поиск возможен только среди всех типов файлов, и эту установку нельзя изменить даже вручную. То, что не передается имя файла в окно, связано, скорее всего, с очередной недоработкой. Замечу, что в обратную сторону передать имя файла, установленное пользователем, объекту Dialog возможно.

    Конечно же, пользователь может остаться неудовлетворенным полученными результатами и иметь желание скорректировать критерии поиска. Я, например, так и сделал. Мои корректировки можно увидеть на следующем рисунке:

    Диалоги с пользователем. Коллекция Dialogs

    Рис. 1.7.  Корректировка критериев поиска

    Усложнив условие поиска, я сократил список найденных файлов, удовлетворяющих условию поиска до трех файлов:

    Диалоги с пользователем. Коллекция Dialogs

    увеличить изображение
    Рис. 1.8.  Новые результаты поиска файлов

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

    Но давайте продолжим рассмотрение нашей программы. После того как пользователь, так или иначе, завершит работу в диалоговом окне, завершит свою работу и метод Show, и программа продолжит свое нормальное выполнение. Рассмотрим, что происходит, когда создается объект HisDialog класса Dialog. Будут ли ему переданы установки, заданные пользователем во время последнего сеанса работы? Ответ половинчатый, - часть параметров передается объекту HisDialog, часть нет. Вот результаты моих экспериментов в окне отладки после формирования объекта HisDialog:


    Type - 99 Вызываемая процедура - FileFind -1 ?HisDialog.SearchName

    ?HisDialog.Name Ch ?HisDialog.SearchPath E:\O2000\DS2000 ?HisDialog.SubDir 1 ?HisDialog.MatchCase 1 ?HisDialog. DateCreatedFrom

    ?HisDialog.Author

    ?HisDialog.Text Execute

    Листинг 1.13.

    (html, txt)

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

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

    Документ и его части

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

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

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

    Гипертекстовый документ. Закладки и гиперссылки

    Гипертекстом называется текст, содержащий ссылки. Конечно, настоящим гипертекстовым документом может быть только документ в электронной форме, поскольку в этом случае становится возможным непосредственный переход по ссылкам. Гипертекстовый документ перестает быть линейным текстом, читаемым от начала до конца. Порядок его чтения (работы с документом) определяется в момент просмотра документа и зависит от выбора гиперссылок лицом, работающим с документом. Когда я говорю о гипертекстовом документе, то применительно к документам Office 2000 это не отражает в полной мере истинную картину. В Office 2000 следует говорить о системе гипертекстовых документов, связанных ссылками. Действительно ссылки в документе Word могут задавать переход к:
  • элементу того же документа, заданного с помощью закладки или стиля Heading,
  • элементу другого документа Word,
  • элементу, расположенному на Web-странице,
  • именованному элементу рабочей книги Excel,
  • именованному слайду Power Point,
  • другому документу, файлу или Web-странице.

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

    Public Sub CreateBookmarks() 'Создание закладок 'Создадим закладку в документе DocOne и свяжем ее с рисунком Dim MyPath As String Documents("DocOne").Activate With ActiveDocument MyPath = .Path .InlineShapes(1).Select 'рисунок мышки .Bookmarks.Add "PictureOfMouse", Selection.Range End With 'Создадим закладку в документе ExampleOfTable и свяжем ее с таблицей Documents.Open (MyPath & "\ExampleOfTable") Documents("ExampleOfTable").Activate With ActiveDocument .Tables(1).Select 'таблица Менделеева .Bookmarks.Add "ТаблицаМенделеева", Selection.Range End With End Sub

    Листинг 1.40.

    (html, txt)

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

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

    Листинг 1.41.

    (html, txt)

    Обратите внимание, метод Add при создании гиперссылок имеет больше параметров, чем при создании закладок. При создании закладки достаточно указать имя закладки и область, с ней связанную. При создании гиперссылки указывается область, связанная с гиперссылкой, точка отправления и точка назначения, заданная двумя параметрами. Параметр Address указывает имя документа, к которому будет осуществлен переход, а параметр SubAddress указывает имя элемента внутри этого документа. Если поле адреса не указано, то переход осуществляется внутри того же документа, где находится точка отправления. Может быть опущено поле, задающее имя элемента, тогда осуществляется переход к самому документу. Одно из этих полей должно быть задано. Именем элемента в документах Word чаще всего выступает имя закладки. В документах Excel, как уже говорилось, в качестве имени элемента может выступать имя, содержащееся в коллекции именNames данного документа, как показано в нашем примере. Заметьте, для двух первых точек отправления, определяющих положение гиперссылки, я выбрал текст, расположенный достаточно глубоко внутри группового объекта TableOfFigures. Сделано это специально, чтобы продемонстрировать некоторые особенности коллекции Hyperlinks документов Word. Взгляните, как выглядит эта часть документа, после установления гиперссылок:

    Гипертекстовый документ. Закладки и гиперссылки

    увеличить изображение
    Рис. 1.13.  Документ с закладками

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


    Public Sub RemoveBookmarks() ' Удаляет по запросу закладки активного документа Dim MyBM As Bookmark Dim Answer As String With ActiveDocument For Each MyBM In .Bookmarks Answer = InputBox(Prompt:="Удалить закладку? " & vbCrLf _ & "Имя закладки - " & MyBM.Name, _ Title:="Удаление закладок", Default:="Да") If Answer = "Да" Then MyBM.Delete

    Next MyBM End With End Sub

    Листинг 1.42.

    (html, txt)

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

    Листинг 1.43.

    (html, txt)

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

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

    Листинг 1.44.

    (html, txt)

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

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


    Public Sub CreateBookmarks() 'Создание закладок 'Создадим закладку в документе DocOne и свяжем ее с рисунком Dim MyPath As String Documents("DocOne").Activate With ActiveDocument MyPath = .Path .InlineShapes(1).Select 'рисунок мышки .Bookmarks.Add "PictureOfMouse", Selection.Range End With 'Создадим закладку в документе ExampleOfTable и свяжем ее с таблицей Documents.Open (MyPath & "\ExampleOfTable") Documents("ExampleOfTable").Activate With ActiveDocument .Tables(1).Select 'таблица Менделеева .Bookmarks.Add "ТаблицаМенделеева", Selection.Range End With End Sub

    Листинг 1.40.

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

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

    Public Sub CreateHyperLinks() 'Создание гиперссылок 'Создадим три гиперссылки в документе DocOne Dim MyPath As String Documents("DocOne").Activate

    With ActiveDocument MyPath = .Path 'гиперссылка- элемент группового объекта TableOfFigures 'свяжем ее с закладкой этого же документа .shapes(2).GroupItems(6).TextFrame.TextRange.Select .Hyperlinks.Add Anchor:=Selection.Range, _ Address:="", SubAddress:="PictureOfMouse"

    'гиперссылка- элемент группового объекта TableOfFigures 'свяжем ее с закладкой другого документа .shapes(2).GroupItems(7).TextFrame.TextRange.Select .Hyperlinks.Add Anchor:=Selection.Range, _ Address:=MyPath & "\ExampleOfTable.doc", _ SubAddress:="ТаблицаМенделеева", _ ScreenTip:="Переход к документу," _ & " содержащему таблицу Менделеева" 'гиперссылка- объект TableOfFigures 'свяжем ее с URL .shapes(1).Select .Hyperlinks.Add Anchor:=Selection.Range, _ Address:="http://www.microsoft.ru/offext/", _ SubAddress:="", _ ScreenTip:="Переход к Web- странице" _ & " программы Office Extensions" End With 'Создадим гиперсссылку в документе ExampleOfTable 'и свяжем ее с закладкой документа DocOne Documents("ExampleOfTable").Activate With ActiveDocument 'Установка гиперссылки возврата в документ Word DocOne .Tables(1).Select 'таблица Менделеева Selection.MoveDown Selection.Expand .Hyperlinks.Add Anchor:=Selection.Range, _ Address:=MyPath & "\DocOne.doc", _ SubAddress:="PictureOfMouse", _ ScreenTip:="Возврат к документу DocOne" 'Установка гиперссылки перехода 'к именованному элементу документа Excel - BookOne Selection.MoveDown Selection.Expand .Hyperlinks.Add Anchor:=Selection.Range, _ Address:=MyPath & "\BookOne.xls", _ SubAddress:="ТаблицаПродаж", _ ScreenTip:="Переход к элементу" _ & " c именем ТаблицаПродаж документа Excel - BookOne"


    End With End Sub

    Листинг 1.41.

    Обратите внимание, метод Add при создании гиперссылок имеет больше параметров, чем при создании закладок. При создании закладки достаточно указать имя закладки и область, с ней связанную. При создании гиперссылки указывается область, связанная с гиперссылкой, точка отправления и точка назначения, заданная двумя параметрами. Параметр Address указывает имя документа, к которому будет осуществлен переход, а параметр SubAddress указывает имя элемента внутри этого документа. Если поле адреса не указано, то переход осуществляется внутри того же документа, где находится точка отправления. Может быть опущено поле, задающее имя элемента, тогда осуществляется переход к самому документу. Одно из этих полей должно быть задано. Именем элемента в документах Word чаще всего выступает имя закладки. В документах Excel, как уже говорилось, в качестве имени элемента может выступать имя, содержащееся в коллекции именNames данного документа, как показано в нашем примере. Заметьте, для двух первых точек отправления, определяющих положение гиперссылки, я выбрал текст, расположенный достаточно глубоко внутри группового объекта TableOfFigures. Сделано это специально, чтобы продемонстрировать некоторые особенности коллекции Hyperlinks документов Word. Взгляните, как выглядит эта часть документа, после установления гиперссылок:

    Гипертекстовый документ. Закладки и гиперссылки

    увеличить изображение
    Рис. 1.13.  Документ с закладками

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

    Public Sub RemoveBookmarks() ' Удаляет по запросу закладки активного документа Dim MyBM As Bookmark Dim Answer As String With ActiveDocument For Each MyBM In .Bookmarks Answer = InputBox(Prompt:="Удалить закладку? " & vbCrLf _ & "Имя закладки - " & MyBM.Name, _ Title:="Удаление закладок", Default:="Да") If Answer = "Да" Then MyBM.Delete


    Next MyBM End With End Sub

    Листинг 1.42.

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

    Public Sub RemoveHyperlinks() ' Удаляет по запросу гиперссылки активного документа Dim MyHL As Hyperlink Dim Answer As String,NameHL As String With ActiveDocument Debug.Print .Hyperlinks.Count For Each MyHL In .Hyperlinks 'определение объекта, с которым связана гиперссылка If MyHL.Type = msoHyperlinkRange Then NameHL = MyHL.Range.Text Else:NameHL = MyHL.Shape.Name End If Answer = InputBox(Prompt:="Удалить гиперссылку? " & vbCrLf _ & "связана с объектом - " &NameHL & vbCrLf _ & "Имя целевого документа - " & MyHL.Address & vbCrLf _ & "Имя целевого элемента - " & MyHL.SubAddress, _ Title:="Удаление гиперссылок", Default:="Да") If Answer = "Да" Then MyHL.Delete

    Next MyHL End With End Sub

    Листинг 1.43.

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

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

    Public Sub FollowHyperlinks() ' Переход по запросу,следуя гиперссылке активного документа Dim MyHL As Hyperlink Dim Answer As String,NameHL As String With ActiveDocument For Each MyHL In .Hyperlinks 'определение объекта, с которым связана гиперссылка If MyHL.Type = msoHyperlinkRange Then NameHL = MyHL.Range.Text Else:NameHL = MyHL.Shape.Name End If Answer = InputBox(Prompt:="Перейти, следуя гиперссылке? " ;amp; vbCrLf _ & "связана с объектом - " &NameHL & vbCrLf _ & "Имя целевого документа - " & MyHL.Address & vbCrLf _ & "Имя целевого элемента - " & MyHL.SubAddress, _ Title:="Переход по гиперссылке", Default:="Да") If Answer = "Да" Then MyHL.Follow MsgBox ("Продолжаем работать!") Exit For End If


    Next MyHL End With End Sub

    Листинг 1.44.

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

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

    Характеристики

    Объект FontNames отвечает за список всех доступных шрифтов. Аналогичные возможности предоставляет команда Font меню Format.
    Объект RecentFiles позволяет работать со списком файлов наиболее позднего использования.
    Объект System ведает информацией о характеристиках компьютера и операционной системы. Приведем пример работы с этим объектом, в котором определяются некоторые рабочие характеристики моего компьютера и окружения:
    Public Sub WorkWithSystem() 'Получение характеристик компьютера и окружения Dim env As System Set env = Application.System 'Debug.Print "Тип компьютера - ", env.ComputerType Debug.Print "Тип процессора - ", env.ProcessorType Debug.Print "Операционная система - ", env.OperatingSystem Debug.Print "Язык - ", env.LanguageDesignation Debug.Print "Свободного дискового пространства - ", env.FreeDiskSpace Debug.Print "Курсор - ", env.Cursor
    End Sub
    Листинг 1.22.
    (html, txt)
    Вот результаты работы этой процедуры:
    Листинг 1.23.
    (html, txt)
    Заметьте, на этой платформе не удается получить тип компьютера.

    Электронные письма

    Объект MailMessage представляет активное EMail-сообщение. Для работы с методами этого объекта помимо активности сообщения требуется дополнительно, чтобы Word использовался, как редактор этого сообщения.
    Несмотря на то, что я выполнил условия, необходимые для работы с этим объектом, ни один из его методов мне вызвать не удалось. Лишь свойство Application этого объекта вызывалось корректно, как показано в следующем примере:
    Public Sub WorkWithMailMessage() Dim mes As MailMessage Documents("test").Activate Set mes = Application.MailMessage If Not (mes Is Nothing) Then Debug.Print mes.Application.Name 'mes. DisplaySelectNamesDialog 'mes.CheckName 'mes.GoToNext 'mes. DisplayMoveDialog End If End Sub
    Листинг 1.17.
    (html, txt)
    Новый появившийся в Office 2000 объект EmailOptions позволяет устанавливать некоторые общие опции, в частности, автоматически добавляемую подпись. В нашем следующем примере добавляемая подпись выбирается в зависимости от выбранного языка. На самом деле выбирается не сама подпись, а ее имя из списка уже подготовленных подписей. Сами подписи должны быть созданы вручную на вкладке Email Options меню Tools|General.
    Public Sub WorkEmail()
    With Application.EmailOptions.EmailSignature
    If Selection.LanguageID = wdRussian Then .NewMessageSignature = "Подпись" .ReplyMessageSignature = "Подпись" Else .NewMessageSignature = "sign1" .ReplyMessageSignature = "sign1" End If Debug.Print .NewMessageSignature
    End With
    End Sub
    Листинг 1.18.
    (html, txt)

    Каркас документа Word

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

    увеличить изображение
    Рис. 1.1.  Каркас по умолчанию документа Word
    Как можно видеть, в каркас входят кроме библиотеки Word еще три библиотеки и два проекта:
  • Word - библиотека, задающая основу документов Word. Здесь хранится класс, задающий корневой объект Word.Application, и все классы объектов, вложенных в корневой объект.
  • Office - библиотека объектов, общих для всех приложений Office 2000. Здесь находятся классы, определяющие инструментальные панели - CommandBar и классы других общих объектов. Здесь же находятся классы, задающие Помощника (объект Assistant и все классы, связанные с ним). В частности, появился новый объект, которого не было в предыдущей версии - Мастер Ответов (Answer Wizard).
  • Stdole - библиотека классов, позволяющая работать с OLE - объектами и реализовать Автоматизацию.
  • VBA - библиотека классов, связанных с языком VBA. Здесь хранятся все стандартные функции и константы, встроенные в язык, классы Collection и ErrObject .
  • Project - проект по умолчанию, связанный с документом. Классы, которые могут программистом создаваться в этом проекте, методы, свойства, - все это доступно для просмотра, так же, как и объекты классов, встроенных в стандартные библиотеки.
  • Normal - проект, доступный для всех документов Word. Здесь могут храниться функции и классы, используемые всеми документами.

  • До сих пор речь шла о каркасе, создаваемом по умолчанию. Но при работе с документом этот каркас можно существенно изменить, добавив в документ новые классы, а значит и новые свойства. Для этого достаточно включить в состав каркаса другие библиотеки классов из той совокупности, что входит в Office 2000. Заметим, набор дополнительно поставляемых библиотек в Office 2000 существенно вырос по сравнению с предыдущей версией.
    А теперь о том, как это делается. Прежде всего, заметим, что расширение каркаса делается обычно руками, но можно делать это и программно. Для того чтобы вручную добавить в документ новые возможности, необходимо вначале войти в режим редактора программного проекта, для чего достаточно в меню Tools | Macro выбрать пункт Visual Basic Editor или нажать комбинацию клавиш Alt + F11. Затем уже в Редакторе из меню Tools следует выбрать пункт References и в появившемся списке возможных библиотек включить те, которые отвечают Вашим потребностям. На рис. 1.2 показан процесс подключения к документу Word новых библиотек.
    Каркас документа Word

    Рис. 1.2.  Расширение возможностей каркаса документа
    В данном примере я подключил библиотеки объектов трех приложений - Outlook, Access и Excel, библиотеку с WEB - классами и загрузчик объявлений, необходимых для вызова API - функций. В результате каркас документа существенно обновился, и документ теперь обладает потенциально большими возможностями, чем документ, создаваемый по умолчанию. В этом документе можно организовать совместную работу четырех приложений Office 2000, работать с WEB - компонентами. Конечно, каркас обеспечивает только потенциальную возможность, чтобы все заработало, нужно многое сделать, наполняя каркас плотью. Например, необходимо описать и создать соответствующие объекты Application для каждого из совместно работающих приложений, прежде чем начать с ними работать.
    На рис. 1.2 можно увидеть лишь небольшую часть всей совокупности библиотек (их несколько десятков), доступных в Office 2000. Завершая разговор о каркасе документа, нам остается сделать два важных замечания. Во-первых, заметим, что включение ссылок на новые библиотеки и соответствующее расширение каркаса документа немедленно отражается при просмотре объектов документа. Взгляните на рис. 1.3, где показано, что теперь все объекты, потенциально включенные в состав документа, стали доступными для просмотра и получения справки:
    Каркас документа Word

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

    Классы, задающие структуризацию текста документа

    Текст - это основа большинства документов. Его можно структурировать, оперируя различными единицами при решении тех или иных задач преобразования текста. Минимальной единицей текста является символ. Другие естественные единицы - это слова, предложения, абзацы. Более крупными частями текста являются страницы, параграфы и главы. Важно уметь оперировать с последовательностями таких единиц. Какие же классы объектов предоставляет Word для этой цели?
    Классы Characters, Words, Statements, Paragraphs позволяют работать с последовательностями (коллекциями) символов, слов, предложений, абзацев. Может показаться удивительным, но классов, соответствующих таким элементам, как символ, слово или предложение, нет. Элементом коллекций Characters, Words и Statements является объект класса Range. Это один из самых важных объектов, необходимых для понимания работы с текстами. Объект Range позволяет работать как с одним символом, так и с последовательностью символов. Документы, поддокументы, абзацы, разделы - все они имеют метод или свойство Range, возвращающее объект Range, представляющий область, связанную с объектом, вызвавшим метод (свойство) Range. Эту область можно рассматривать как интервал, задаваемый первым и последним символом текста данной области. Поэтому работа с текстом, так или иначе, ведется через методы и свойства объекта Range.
    Чуть позже мы продолжим знакомство с классами Characters, Words, Statements, Paragraphs и Range.

    Коллекции объекта Document

    Рассмотрим список коллекций, входящих в состав объекта Document:
  • Bookmarks
  • Characters (Range)
  • CommandBars
  • Comments
  • DocumentProperties
  • Endnotes
  • Fields
  • Footnotes
  • FormFields
  • Frames
  • Hyperlinks
  • Indexes
  • InlineShapes
  • HorizontalLineFormat

  • ListParagraphs
  • Lists
  • ListParagraphs
  • Range

  • ListTemplates
  • ListLevels
  • Font

  • Paragraphs
  • ProofreadingErrors (Range)
  • Revisions
  • ReadabilityStatistics
  • Scripts
  • Sections
  • Sentences (Range)
  • Shapes
  • StoryRanges (Range
  • Styles
  • Subdocuments
  • Tables
  • TablesOfAuthoritiesCategories (TableOfAuthoritiesCategory)
  • TablesOfAuthorities (TableOfAuthorities)
  • TablesOfContents (TableOfContents)
  • TablesOfFigures (TableOfFigures)
  • Variables
  • Versions
  • Windows
  • Words (Range)

  • Среди объектов, вложенных в объект Document на первом уровне иерархии, коллекции составляют явное большинство - их 36. Каждая коллекция содержит элементы одного класса. Как правило, имя класса коллекции строится как множественное число (по правилам английского языка) от имени класса элемента коллекции. Например, коллекция Documents содержит объекты класса Document, а коллекция Paragraphs содержит объекты класса Paragraph. В тех случаях, когда это правило не выполняется, в скобках указано имя класса для объектов, входящих в коллекцию. Заметьте, например, что коллекции предложений, слов и символов документа Word состоят из объектов класса Range.
    Для некоторых из объектов этого списка указаны и встроенные в них объекты, лежащие на следующем уровне иерархии. В этих случаях курсивом выделены те объекты, которые не являются коллекциями.

    Конверторы

    Word позволяет работать с документами, подготовленными не только в более ранних его версиях, но и в других текстовых редакторах. Для этого используются конверторы файлов. Коллекция объектов FileConverters позволяет выбрать тот или конвертор. У коллекции нет методов Add и Delete, поэтому добавлять новые конверторы или удалять ненужные конверторы программно нельзя. Это делается в процессе инсталляции Office 2000. В следующем примере печатаются имена конверторов, доступных на моей машине и анализируются некоторые их свойства:
    Public Sub ConvertDoc() 'Работа с конвертором Dim conv As FileConverter Debug.Print Application.FileConverters.Count For Each conv In Application.FileConverters Debug.Print conv.Name, conv.FormatName, conv.ClassName If conv.CanOpen And conv.CanSave Then Debug.Print "Конвертор может открывать и сохранять файлы" End If Next conv End Sub
    Листинг 1.20.
    (html, txt)
    Приведем результаты работы этой процедуры. Нет смысла приводить полный список всех 26 доступных конверторов, поэтому мы ограничимся несколькими строчками:
    26 Text with Layout Text with Layout Конвертор может открывать и сохранять файлы MS-DOS Text with Layout MS-DOS Text with Layout Конвертор может открывать и сохранять файлы WordPerfect 5.x for Windows WrdPrfctWin WordPerfect 5.1 for DOS WrdPrfctDOS51
    Листинг 1.21.
    (html, txt)

    Методы объекта Application

    Казалось бы, объекты Application не должны иметь много методов, а среди имеющихся методов большая часть должна быть общей для приложений разных типов. Ведь специфика приложений должна проявляться на более низком уровне иерархии, когда начинается работа, например, с документами Word или рабочими книгами Excel. Но это не так. Реально методов много, а общих из них - мало. Более того, даже методы Activate, Run, Quit, имеющиеся у всех приложений, совпадающие по именам и предназначенные для решения стандартных задач (активизации приложения, запуска макроса, выхода из приложения), организованы по-разному.
    Я не буду проводить подробного разбора методов этого объекта, а ограничусь лишь кратким обзором, указав для большинства методов их назначение и аргументы.

    Таблица 1.3. Методы объекта ApplicationСинтаксисОписание аргументовНазначение
    Sub Activate()НетАктивизирует приложение
    Sub AddAddress(TagID() As String, Value() As String)Первый массив задает имена компонент адреса, второй их значения. Имена компонент фиксированы и задаются специальными константами, например, pr_given_name, pr_surname.Добавляет новый адрес в адресную книгу
    Sub AutomaticChange()НетВыполняет автоформатирование, когда есть изменения, предлагаемые Помощником (Office Assistant).
    Function BuildKeyCode(Arg1 As WdKey, [Arg2], [Arg3], [Arg4]) As LongАргументы задают комбинацию клавишей, обычно, это пара клавиш, например ALT и F1. Клавиши задаются константами перечисления WdKey.Возвращает уникальный код каждой комбинации "горячих" клавиш.
    Sub ChangeFileOpenDirectory(Path As String)Путь к каталогу.Устанавливает каталог по умолчанию, открываемый в приложении Word при выполнении команды Open.
    Function CheckGrammar(String As String) As BooleanПроверяемая строка.Выполняет проверку грамматической корректности строки, указанной в аргументе. Заметьте, аналогичный метод у объектов на нижнем уровне иерархии проверяет корректность текста этих объектов.
    Function CheckSpelling(Word As String, [CustomDictionary], [IgnoreUppercase], [MainDictionary], [CustomDictionary2], … [CustomDictionary10]) As BooleanПервый обязательный аргумент задает проверяемую строку. Остальные необязательные аргументы задают словари и условия проверки.Выполняет проверку орфографии строки, заданной первым аргументом.
    Function CleanString(String As String) As StringСтрока преобразованияУдаляет или преобразовывает непечатаемые и специальные символы строки, заданной своим аргументом.
    Function DefaultWebOptions() As DefaultWebOptionsНетВозвращает соответствующий объект, содержащий глобальные параметры уровня приложения, используемые Word всякий раз при сохранении или открытии документа как Web-страницы.
    Function GetAddress([Name], [AddressProperties], [UseAutoText], [ DisplaySelectDialog], [SelectDialog], [CheckNamesDialog], [RecentAddressesChoice], [UpdateRecentAddresses]) As StringПервый возможный аргумент указывает имя адресата, остальные определяют специфику поиска адреса.Ищет адрес в адресной книге. Аргументы определяют специфику выполнения этой операции.
    Function GetDefaultTheme(DocumentType As WdDocumentMedium) As StringТип, заданный константой из перечисления WdDocumentMediumВозвращает имя темы (фонового узора) по умолчанию, принятого в Word для новых документов, или сообщений Email или Web-страниц.
    Sub GoBack(), Sub GoForward()НетВ активном документе передвигает вперед и назад точку вставки между тремя последними точками редактирования.
    Sub Help(HelpType)Константа, заданная перечислением WdHelpTypeВ зависимости от значения константы отображает то или иное окно справочной системы. Чаще всего, открывает окно Office Assistant со списком тем справочной системы.
    Sub HelpTool()НетИзменяет указатель мыши со стрелки на знак вопроса, что будет указывать на получение контекстно-чувствительной справки при подведении указателя к некоторому элементу активного документа. Нажатие Esc возвращает прежний вид указателя.
    Function Keyboard([LangId As Long]) As LongЕсли аргумент опущен, то возвращается значение, представляющее комбинацию чисел, определяющих язык и раскладку клавиатуры. Для установки языка и раскладки необходимо задать нужную комбинацию значений. Устанавливает или возвращает язык и раскладку клавиатуры.
    Function KeyString(KeyCode As Long, [KeyCode2]) As StringПервый обязательный аргумент задает код, определяющий комбинацию горячих клавиш. Возвращает по коду строку, задающую комбинацию горячих клавиш. Функция, обратная по своему действию функции BuildKeyCode.
    Sub ListCommands(ListAllCommands As Boolean)Если аргумент имеет значение true, то выводится весь список команд Word, в противном случае - команд, прошедших настройку в приложении. Создает новый документ, содержащий таблицу со списком команд Word. Полный список содержит 1175 команд. Для каждой команды указывается комбинация горячих клавиш и инструментальная панель меню.
    Sub LookupNameProperties(Name As String)Задает имя в адресной книгеОткрывает диалоговое окно свойств для адресата, имя которого указано в аргументе метода.
    Sub Move(Left As Long, Top As Long)Координаты окнаПередвигает окно активного документа в позицию, указанную аргументами.
    Function NewWindow() As WindowНетСоздает новое окно - копию активного окна активного документа.
    Sub OnTime(When,Name As String, [Tolerance])Первый аргумент задает время, второй - имя макроса.Включает будильник и запускает макрос в заданное время.
    Sub OrganizerCopy(Source As String, Destination As String,Name As String, Object As WdOrganizerObject)Source - имя файла - источника, Destination - имя файла-назначения,Name - имя копируемого элемента, Object - тип копируемого элемента, задается константой.Копирует элемент, заданный аргументомName из документа или шаблона в документ или шаблон. Копируемый элемент может быть входом AutoText, инструментальной панелью, стилем или макросом, что определяется аргументом Object.
    Sub OrganizerDelete(Source As String,Name As String, Object As WdOrganizerObject)Смотри описание аргументов предыдущего метода Copy.Выполняет операцию удаления элемента.
    Sub OrganizerRename(Source As String,Name As String, NewName As String, Object As WdOrganizerObject)Смотри описание аргументов метода Copy.Выполняет операцию переименования элемента.
    Sub PrintOut([Background], [Append], [Range], [OutputFileName], [From], [To], [Item], [Copies], [Pages], [PageType], [PrintToFile], [Collate], [FileName], [ActivePrinterMacGX], [ManualDuplexPrint], [PrintZoomColumn], [PrintZoomRow], [PrintZoomPaperWidth], [PrintZoomPaperHeight])Аргументы соответствуют опциям в диалоговом окне Print меню File.Печатает все части документа.
    Function ProductCode() As StringНет.Возвращает глобальный идентификатор - GUID приложения Word.
    Sub Quit([SaveChanges], [OriginalFormat], [RouteDocument])Задают опции в момент закрытия приложения.Закрывает все открытые документы с их возможным сохранением и пересылкой всем участникам, совместно работающим над документами.
    Function Repeat([Times]) As BooleanЧисло повторений последней команды редактированияВыполняет заданное число раз последнюю команду редактирования, возвращает истину в случае успеха.
    Sub Resize(Width As Long, Height As Long)Размеры окна приложения Word. Изменяет размеры окна приложения Word.
    Function Run(MacroName As String, [varg1], … [varg30])Имя макроса и до 30 передаваемых ему аргументов.Запускает макрос VBA.
    Sub ScreenRefresh()Нет.Запускает Fax Wizard.
    Sub SetDefaultTheme(Name As String, DocumentType As WdDocumentMedium)Name задает имя темы и параметры форматирования. Формат этого параметра "имя_темы nnn". Второй аргумент задает тип документа Устанавливает для заданного типа документа тему ( фоновый узор) и способ форматирования. Смотри также описание метода GetDefaultTheme.
    Группа DDE-методов: Sub DDEExecute(Channel As Long, Command As String) и другие методы этой группы.Обеспечивает динамический обмен данными между приложениями.
    Группа методов преобразования линейных мер: Function CentimetersToPoints(Centimeters As Single) As Single Function PointsToCentimeters(Points As Single) As Single И другие методы этой группы.Мера длины в указанных единицах.Задает взаимное преобразование между точками и другими мерами длины. Возвращает значение меры, заданной аргументом, в других единицах.


    Группа методов преобразования линейных мер: Function CentimetersToPoints(Centimeters As Single) As Single Function PointsToCentimeters(Points As Single) As Single И другие методы этой группы. Мера длины в указанных единицах. Задает взаимное преобразование между точками и другими мерами длины. Возвращает значение меры, заданной аргументом, в других единицах. Как видите, набор методов объекта Application разнообразен. Некоторые из них вполне естественны для этого объекта, как, например, методы работы с окном приложения, другие, на мой взгляд, довольно экзотичны, - например, методы работы со строкой текста, проверяющие ее корректность. Если попытаться классифицировать эти методы, то условно их можно разбить на следующие группы, каждая из которых позволяет работать с таким объектом, как:

  • Окно приложения.
  • Адресная книга.
  • Каталоги, используемые приложением по умолчанию.
  • Форматирование, в том числе темы, используемые в Web-страницах.
  • Редактирование.
  • Справочная система.
  • Строки.
  • Средства запуска макросов, в том числе в заданное время.
  • Организатор (Organizer).
  • Динамический обмен данными.


  • Настройка

    С помощью объекта Options можно программным путем установить различные опции приложения и документа аналогично тому, как если бы Вы выбрали команду Options (Параметры) в меню Tools (Сервис). При работе вручную это один из наиболее часто выбираемых пунктов меню. В открывающемся окне десяток флажков, вкладок и плюс к этому пара командных кнопок. Понятно, что и соответствующий объект Options имеет десятки свойств, задавая значения которых можно программно настроить приложение нужным образом. Методов этот объект фактически не имеет. Для демонстрации я выбрал некоторое множество опций. Заметьте, что в большинстве случаев включение или отключение того или иного свойства приложения Word, распространяется на все его документы:
    Public Sub WorkWithOptions() 'работа с объектом Options Options.AutoFormatAsYouTypeReplaceFractions = True Options.AllowDragAndDrop = True Options.CheckGrammarWithSpelling = True Options.EnableSound = False Options.MonthNames = wdMonthNamesEnglish Options.PrintComments = True End Sub
    Листинг 1.19.
    (html, txt)

    Объект Word.Application

    Каркас любого документа Word, как уже говорилось, составляют объекты разных классов, объединенные в библиотеки. Для документов Word основной библиотекой, конечно же, является библиотека классов Word9. Центральным объектом каркаса документа является объект этой библиотеки Application, определяющий само приложение. Поскольку в основе построения объектов Office лежит встраивание, то объекты Office 2000 являются, как правило, "толстыми" объектами, в каждый из которых встроены другие объекты.
    Объект Application это один из самых "толстых" объектов, в него встроены объекты, задающие различные компоненты приложения Word. В программном проекте любого из открытых документов Word доступен корневой объект Application, определяющий само приложение. Одновременно становятся доступными и все встроенные в него объекты. В частности, становится доступной коллекция Documents всех открытых документов Word, а тем самым, и объект, задающий наш, например, только что открытый документ.
    В приложении Word на разных уровнях иерархии определено около 200 объектов, совокупность которых и определяет мир объектов Word, его возможности, свойства и поведение. Сам корневой объект Word.Application имеет более сотни элементов: свойств, методов и событий. Объекты Application различных приложений Office 2000 - Word.Application, Excel.Application и другие составляют семейство схожих объектов.
    Объект Application, задающий приложение, естественно, определяет свойства и поведение приложения в целом. Как я уже говорил, он содержит коллекцию документов данного приложения - документы в приложении Word, рабочие книги в Excel, презентации - в Power Point. В этот же объект непосредственно вложены общие объекты Office 2000 - Assistant, CommandBars, VBE, AddIns, ComAddIns и другие. Сюда же вложены и многие объекты, схожие для приложений Office 2000, - Windows, Dialogs и другие.
    Рассмотрим теперь более подробно и вначале чисто формально состав объекта Word.Application. Такое алфавитное перечисление и "шапочное" знакомство тоже полезно. Позже мы познакомимся со многими из этих элементов уже по существу. Начнем наше рассмотрение с коллекций объектов, встроенных в объект Application:

    Таблица 1.1. Коллекции объектов, встроенных в корневой объект Word ApplicationКоллекцияСвойстваМетодыНовые коллекции Office 2000 СвойстваМетоды
    AddIns (AddIn),Application, Count, Creator, ParentAdd, Item, UnLoad
    AutoCaptions (AutoCaption),Application, Count, Creator, ParentItem, CancelAutoInsert
    CaptionLabels (CaptionLabel),Application, Count, Creator, ParentAdd, Item
    CommandBars (CommandBar),Application, Count, Creator, Parent (+ 6 дополнительных свойств)Add, Item, FindControl, ReleaseFocus
    Dialogs (Dialog),Application, Count, Creator, ParentItem
    Dialogs (Dialog),Application, Count, Creator, ParentItem
    Dictionaries (Dictionary),Application, Count, Creator, Parent, ActiveCustomDictionary, MaximumAdd, Item, ClearAll
    Documents (Document)Application, Count, Creator, ParentAdd, Open, Item, Close, Save
    FileConverters (FileConverter),Application, Count, Creator, Parent, ConvertMacWordChevronsItem
    KeysBoundTo (KeyBinding),Application, Count, Creator, Parent, Command, CommandParametr, Context, KeyCategoryItem, Key
    KeyBindings (KeyBinding),Application, Count, Creator, Parent, ContextAdd, Item, Key, ClearAll
    Languges (Language),Application, Count, Creator, Parent, ContextItem
    ListGalleries (ListGallery),Application, Count, Creator, Parent, ContextItem
    RecentFiles (RecentFileApplication, Count, Creator, Parent, MaximumAdd, Item
    SpellingSuggestions (SpellingSuggestion),Application, Count, Creator, Parent, SpellingErrorTypeItem
    Tasks (Task),Application, Count, Creator, ParentItem, Exists, ExitWindow
    Templates (Template),Application, Count, Creator, ParentItem
    Windows(Window).Application, Count, Creator, ParentAdd, Item, Arrange
    COMAddInsApplication, Count, Creator, ParentItem, Update
    DefaultWebOptionsBrowserLevel, Encoding, OrganizeInFolder и другие, всего 19 свойств нет
    HangulHanjaConversionDictionariesActiveCustomDictionary, Application, BuiltinDictionary, Count, Creator, Maximum, ParentAdd, ClearAll, Item
    <
    Большинство из коллекций содержит типичные для коллекций свойства и методы: Add - для добавления нового элемента в коллекцию, Item для выбора элемента из коллекции по ключу или индексу, свойство Count возвращает число элементов в коллекции, Parent - родительский объект. У некоторых из коллекций имеются дополнительные свойства и/или методы. Не менее важно то, что в некоторых коллекциях нет того или иного типичного для коллекций метода, например метода Add. Так, у коллекции SpellingSuggestions нет методов для удаления и добавления элементов, так как пользователь не имеет возможности добавлять собственные предположения или удалять имеющиеся. У коллекции Templates, также не имеющей этих методов, ситуация другая - ее элементы добавляются и удаляются при работе с коллекцией Documents, элементами которой являются как документы, так и шаблоны.

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

    Взгляните на вложенные в объект Application простые объекты, не являющиеся коллекциями:

    Таблица 1.2. Объекты, встроенные в Word ApplicationОбъектСвойстваМетодыНовые объекты в Office 2000СвойстваМетоды
    AssistantApplication, Creator, Parent + 21 свойствоActivateWizard, StartWizard, EndWizard, Help, ResetTips, Move
    AutoCorrectApplication, Creator, Parent + 3 свойстваМетодов нет
    BrowserApplication, Creator, Parent, TargetNext, Previous
    FileSearchApplication, Creator + 10 свойствExecute, NewSearch
    FontNamesApplication, Creator, Parent, CountItem
    MailingLabelApplication, Creator, Parent + 4 свойстваCreateNewDocument, PrintOut
    MailMessageApplication, Creator, ParentDelete, Reply, Forward, GotoNext, GotoPrevious + 6 методов
    Options100 различных свойствSetWPHelpOptions
    SetWPHelpOptionsБолее 50 свойствБолее 70 методов
    SystemApplication, Creator, Parent + 15 свойствConnect, MSInfo
    SynonymInfoApplication, Creator,Parent + 9 свойствМетодов нет
    VBE18 свойств Quit
    AnswerWizardApplication, Creator, Files, ParentClearFileList, ResetFileList
    EmailOptionsEmailSignature, ReplyStyle и другие, всего 10 свойствнет
    LanguageSettingsApplication, Creator, LanguageId, LanguagePrefferedForEditingнет


    В Office 2000 объект Application немножко "растолстел", и у него появились три новых объекта. Объект AnswerWizard позволяет организовать выдачу собственных подсказок при обращении пользователя к справочной системе. Его основное свойство Files возвращает коллекцию файлов AnswerWizardFiles, метод Add которой позволяет добавлять новые файлы с подсказками. Объекты EmailOptions и LanguageSettings позволяют устанавливать и анализировать значения опций при работе с почтой и языками, используемыми в документах.

    Приведенный список коллекций и объектов далеко не полностью отражает всю объектную структуру приложения Word. Это лишь видимая часть айсберга. Многие из вложенных объектов, например Document, не менее сложны по своей внутренней структуре. Возникает естественный вопрос, как же во всем этом разобраться и как это все можно запомнить? Разобраться во всем этом стоит, а вот запоминать не обязательно. Объектная природа построения приложения позволяет успешно работать только с нужными в конкретной ситуации объектами, используя только часть их свойств и методов и, возможно, не зная о существовании других объектов. Более важно знание инструментальных средств Office 2000, к которым часто приходится обращаться при работе с объектами. Интеллектуальная система поддержки работы с объектами, - то, что называется IntelliSense, окно просмотра или браузер объектов, Помощник и справочная система, все эти средства позволяют избежать запоминания деталей.

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

    Переменные, которые живут долго

    У переменных век не долог. Локальные переменные, как правило, заканчивают существование в момент окончания работы процедуры, в которой они описаны. Глобальные переменные живут дольше, некоторые из них могут быть доступными в разных программных проектах системы документов в процессе работы с этими документами.
    Однако и локальные и глобальные переменные "умирают" по окончании сеанса работы. Для того чтобы сохранять данные между сеансами работы, необходимо использовать файлы, базы данных и другие внешние источники даных. Замечу, что, конечно, есть еще возможность хранить данные, как часть самого документа, например, в таблицах Word или Excel. Но сейчас мы поговорим еще об одной интересной возможности, предоставляемой при работе с офисными документами. С каждым из документов можно связать коллекцию переменных типа Variant коллекцию Variables(Variable). Эта коллекция для программистов важна - ведь время жизни входящих в нее переменных совпадает со временем жизни документа. По существу речь идет о некотором специальном файле переменных, жестко связанном с самим документом и хранящимся вместе с ним. Тем самым появляется возможность сохранять информацию о работе документе между сеансами. Применения этого полезного средства могут быть самыми разными. Можно сохранять предпочтения, сделанные пользователем при первом сеансе работы с документом, различные настройки документа и так далее. Другое применение связано с предоставлением документа во временное владение с ограничением числа возможных запусков. Например, можно иметь счетчики, подсчитывающие количество вызовов некоторой процедуры и в зависимости от значения счетчика по-разному определять дальнейшую работу с данным документом. Переменные, входящие в коллекцию Variables, создаются не так, как обычные переменные, нет необходимости в их описании в разделе объявлений какого либо модуля. Они имеют фиксированный тип Variant и создаются методом Add, типичным для создания элементов коллекций. Этот метод имеет достаточно прозрачный синтаксис:

    Листинг 1.52.

    (html, txt)

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

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

    Public Sub CreateVar() 'Создание переменных - хранителей информации With ActiveDocument.Variables If Not ExistVar("Counter") Then 'Добавляем переменную .AddName:="Counter", Value:=0 End If End With

    End Sub

    Public Function ExistVar(Name As String) As Boolean 'Определяет наличие переменнойName в коллекции Variables Dim MyVar As Variable ExistVar = False For Each MyVar In ActiveDocument.Variables If MyVar.Name =Name Then ExistVar = True: Exit For End If Next MyVar End Function

    Листинг 1.53.

    (html, txt)

    При попытке повторного добавления одной и той же переменной в коллекцию Variables возникает ошибка. В тоже время процедура, в которой происходит добавление, может выполняться многократно. Поэтому я предусматриваю соответствующую проверку на существование переменной с заданным именем в коллекции Variables. Может возникнуть вопрос, куда поместить процедуру CreateVar, создающую переменную. Я поместил ее в процедуру, обрабатывающую событие "Open" документа, но можно упрятать этот вызов и более глубоко. Рассмотрим теперь, как происходит работа с переменной Counter:

    Public Sub CheckCounter() Const Limit = 10 'Счетчик Counter может быть использован в любой процедуре, 'позволяя следить за числом ее выполнения With ActiveDocument If .Variables("Counter") > Limit Then 'Исчерпан лимит нормальной работы демо-версии Call MsgBox("Исчерпан лимит работы демо-версии", _ vbCritical, "Конец работы!") Else ' продолжаем нормальную работу Dim myLocal As Integer 'Локальные переменные могут работать с глобальным счетчиком myLocal = .Variables("Counter") Debug.Print "Счетчик = "; myLocal


    ' В конце работы увеличиваем значение счетчика myLocal = myLocal + 1 .Variables("Counter") = myLocal End If End With End Sub

    Листинг 1.54.

    (html, txt)

    Экспериментируя с тестовым документом, я открывал и закрывал его многократно, время от времени, вызывая процедуру CheckVar. После 11 ее запусков нормальное выполнение было прервано и выдалось предупреждающее сообщение:

    Переменные, которые живут долго

    увеличить изображение
    Рис. 1.17.  Документ в момент выдачи предупреждающего сообщения.

    Перемещение по тексту. Объект Browser

    Объект Browser позволяет перемещать точку вставки, указывающую на объекты в документе. Перейти можно к предыдущей или следующей точке редактирования документа. Можно также предварительно выбрать целевой объект, это может быть таблица, заголовок, комментарий или другой из некоторого набора возможных объектов, а затем переходить к следующему или предыдущему объекту выбранного типа. Вручную это делается с помощью трех инструментальных кнопок, расположенных в самом низу вертикальной полосы прокрутки окна документа. Покажем, как программно, перемещаясь по заголовкам, можно помочь пользователю найти в документе нужный ему заголовок. Вот соответствующая процедура:
    Листинг 1.10.
    (html, txt)
    on_load_lecture()
    Перемещение по тексту. Объект Browser
    Перемещение по тексту. Объект Browser
    Дальше "
    Перемещение по тексту. Объект Browser
    Если Вы заметили ошибку - сообщите нам.
    Перемещение по тексту. Объект Browser
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    5
    |
    6
    |
    7
    |
    8
    |
    9
    |
    10
    |
    11
    |
    12
    |
    вопросы | "
    |
    для печати и PDA
    Перемещение по тексту. Объект Browser
    Перемещение по тексту. Объект Browser
    Перемещение по тексту. Объект Browser

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Письма

    Существует несколько объектов, которые могут быть полезны при работе с почтой. Объект MailingLabel позволяет работать с конвертами и адресами. Он позволяет распечатать адрес на конверте или создать страницу с адресами, которую можно затем распечатать и использовать адреса как наклейки. Его возможности во многом совпадают с тем, что можно делать вручную при выборе пункта Envelopes and Labels (Конверты и наклейки) меню Tools (Сервис). Приведем пример работы с этим объектом. В этом примере один адрес печатается непосредственно на конверте, а для другого, отмеченного специальной закладкой с именем EnvelopeAddress, создается документ с наклейками:
    Public Sub WorkWithMail() 'Работа с почтовыми сообщениями Dim MyAddr As String, MyName As String Dim MailLab As CustomLabel MyName = Application.MailingLabel.DefaultLabelName Debug.Print MyName
    Set MailLab = Application.MailingLabel.CustomLabels _ .Add(Name:="My Friend", DotMatrix:=True) MyAddr = "Россия" & vbCrLf & "Мой город" & vbCr & "Моя улица, 41, 7" & vbCr _ & "Моему другу" 'MailLab.PageSize = wdCustomLabelLetter If Documents("DocOne").Bookmarks.Exists("EnvelopeAddress") Then Application.MailingLabel.PrintOut _ Name:=MyName, ExtractAddress:=True, SingleLabel:=True End If
    Application.MailingLabel.CreateNewDocument _ Name:="My Friend", Address:=MyAddr
    End Sub
    Листинг 1.16.
    (html, txt)
    Мне не удалось установить размер страницы у объекта MailLab класса CustomLabel. Полагаю, что это небольшой "жучок", не имеющий, впрочем, принципиального значения.

    Поиск файлов

    Объект Filesearch позволяет найти нужный файл. Это общий объект, и о нем подробно рассказывается в лекции 5

    Выключаю опции Application. DisplayStatusBar

    Public Sub WorkWithTerm() 'Работа с терминальными свойствами ' Выключаю опции Application. DisplayStatusBar = False Application. DisplayRecentFiles = False
    End Sub
    Листинг 1.1.
    Закрыть окно

    с объектом Browser Dim myrprev

    Public Sub WorkWithBrowser() 'Работа с объектом Browser Dim myrprev As Range, myrnext As Range, Answer As String 'Поиск нужного заголовка в диалоге с пользователем 'Установить заголовок в качестве цели поиска Application.Browser.Target = wdBrowseHeading With ActiveDocument 'Встать в начало документа .Paragraphs.First.Range.Select Set myrnext = Selection.Range Do Answer = InputBox("Это искомый заголовок? (Да/Нет)", "Заголовки", "Нет") If Answer = "Да" Then Exit Do 'Передвинуться к следующему заголовку Application.Browser.Next Set myrprev = myrnext Selection.MoveEnd (wdParagraph) Set myrnext = Selection.Range Loop Until (myrprev = myrnext)
    If Answer = "Нет" Then MsgBox ("В данном документе нет других заголовков стиля Heading") End If End With End Sub
    Листинг 1.10.
    Закрыть окно

    Предоставление пользователю возможности открыть любой

    Public Sub WindowOpenFile() ' Предоставление пользователю возможности открыть любой файл. 'Открывается каталог, хранящий активный документ, с показом всех файлов. Dim Act As Long With Dialogs (wdDialogFileOpen) .Name = ActiveDocument.Path & "\*.*" Act = . Show Debug.Print Act If Act = -1 Then 'Файл успешно открыт и можно получить путь к нему и его имя Debug.Print Documents(1).Path & "\" & Documents(1).Name End If End With
    End Sub
    Листинг 1.11.
    Закрыть окно

    Dim Act As Long Dim

    Public Sub DialogFileFind() 'Предоставление пользователю возможности найти файл.
    Dim Act As Long Dim HisDialog As Dialog
    With Dialogs (wdDialogFileFind) Debug.Print "Type - ", .Type, "Вызываемая процедура - ", .CommandName 'Метод Update задает установки по умолчанию для ряда параметров. 'Некоторые параметры сохраняют установки предыдущего вызова FileFind .Update
    'Предварительное задание параметров перед открытием диалогового окна. 'Не все из заданных значений передаются в окно. .SearchName = "*.doc" .Name = "Ch6" .SearchPath = "e:\O2000\DS2000" .SubDir = 1 .MatchCase = 1 . DateCreatedFrom = "20.05.2000" .Author = "Vladimir Billig" .Text = "файлов" .Execute Act = . Show If Act = -1 Then MsgBox (Documents(1).Name) Debug.Print Act End With Set HisDialog = Dialogs (wdDialogFileFind) With HisDialog .Execute Act = . Show End With End Sub
    Листинг 1.12.
    Закрыть окно

    Вызываемая процедура

    Type - 99 Вызываемая процедура - FileFind -1 ?HisDialog.SearchName
    ?HisDialog.Name Ch ?HisDialog.SearchPath E:\O2000\DS2000 ?HisDialog.SubDir 1 ?HisDialog.MatchCase 1 ?HisDialog. DateCreatedFrom
    ?HisDialog.Author
    ?HisDialog.Text Execute
    Листинг 1.13.
    Закрыть окно

    Dim Lang As Language, Dict

    Public Sub WorkLang() Dim Lang As Language, Dict As Dictionary Dim AppLang As Long, InterfaceLang As Long, HelpLang As Long
    'Орфографический словарь Set Lang = Languages(wdRussian) Set Dict = Lang.ActiveSpellingDictionary Debug.Print Lang.Name If Not (Dict Is Nothing) Then Debug.Print Dict.Path & Application.PathSeparator & Dict.Name End If 'Установки языка общения AppLang = Application. LanguageSettings.LanguageID(msoLanguageIDInstall) InterfaceLang = Application. LanguageSettings.LanguageID(msoLanguageIDUI) HelpLang = Application. LanguageSettings.LanguageID(msoLanguageIDHelp) If AppLang = (InterfaceLang) And (AppLang = HelpLang) _ And (AppLang = wdRussian) Then Debug.Print "Установлен русский язык" _ & " в качестве языка инсталляции, интерфейса и справки" ElseIf AppLang = (InterfaceLang) And (AppLang = HelpLang) _ And (AppLang = wdEnglishUS) Then Debug.Print "Установлен английский язык" _ & " в качестве языка инсталляции, интерфейса и справки" End If
    If Application. LanguageSettings. _ LanguagePreferredForEditing(msoLanguageIDRussian) Then Debug.Print "Русский язык является" _ & " одним из предпочтительных языков редактирования" End If End Sub
    Листинг 1.14.
    Закрыть окно

    LEX Русский Установлен английский язык

    'C:\Program Files\Common Files\Microsoft Shared\Proof\MSSP_RU. LEX Русский Установлен английский язык в качестве языка инсталляции, интерфейса и справки Русский язык является одним из предпочтительных языков редактирования.
    Листинг 1.15.
    Закрыть окно

    с почтовыми сообщениями Dim MyAddr

    Public Sub WorkWithMail() 'Работа с почтовыми сообщениями Dim MyAddr As String, MyName As String Dim MailLab As CustomLabel MyName = Application.MailingLabel.DefaultLabelName Debug.Print MyName
    Set MailLab = Application.MailingLabel.CustomLabels _ .Add(Name:="My Friend", DotMatrix:=True) MyAddr = "Россия" & vbCrLf & "Мой город" & vbCr & "Моя улица, 41, 7" & vbCr _ & "Моему другу" 'MailLab.PageSize = wdCustomLabelLetter If Documents("DocOne").Bookmarks.Exists("EnvelopeAddress") Then Application.MailingLabel.PrintOut _ Name:=MyName, ExtractAddress:=True, SingleLabel:=True End If
    Application.MailingLabel.CreateNewDocument _ Name:="My Friend", Address:=MyAddr
    End Sub
    Листинг 1.16.
    Закрыть окно

    Dim mes As MailMessage

    Public Sub WorkWithMailMessage() Dim mes As MailMessage Documents("test").Activate Set mes = Application.MailMessage If Not (mes Is Nothing) Then Debug.Print mes.Application.Name 'mes. DisplaySelectNamesDialog 'mes.CheckName 'mes.GoToNext 'mes. DisplayMoveDialog End If End Sub
    Листинг 1.17.
    Закрыть окно

    Public Sub

    Public Sub WorkEmail()
    With Application.EmailOptions.EmailSignature
    If Selection.LanguageID = wdRussian Then .NewMessageSignature = "Подпись" .ReplyMessageSignature = "Подпись" Else .NewMessageSignature = "sign1" .ReplyMessageSignature = "sign1" End If Debug.Print .NewMessageSignature
    End With
    End Sub
    Листинг 1.18.
    Закрыть окно

    с объектом Options

    Public Sub WorkWithOptions() 'работа с объектом Options Options.AutoFormatAsYouTypeReplaceFractions = True Options.AllowDragAndDrop = True Options.CheckGrammarWithSpelling = True Options.EnableSound = False Options.MonthNames = wdMonthNamesEnglish Options.PrintComments = True End Sub
    Листинг 1.19.
    Закрыть окно

    В этом примере используется свойство

    Public Sub IsObjectProp() ' В этом примере используется свойство IsObjectValid Dim MyPath As String Dim SecondDoc As Document MyPath = ActiveDocument.Path 'Определяем объект Set SecondDoc = Documents.Open(MyPath & "\DocTwo.doc") 'Удаляем объект, возможно, по ошибке Documents(1).Close 'Теперь нам объект понадобился If Not IsObjectValid(SecondDoc) Then 'Добавляем элемент в коллекцию Set SecondDoc = Documents.Open(MyPath & "\DocTwo.doc") End If 'Работа с документом DocTwo Debug.Print SecondDoc.Name
    End Sub
    Листинг 1.2.
    Закрыть окно

    с конвертором Dim conv As

    Public Sub ConvertDoc() 'Работа с конвертором Dim conv As FileConverter Debug.Print Application.FileConverters.Count For Each conv In Application.FileConverters Debug.Print conv.Name, conv.FormatName, conv.ClassName If conv.CanOpen And conv.CanSave Then Debug.Print "Конвертор может открывать и сохранять файлы" End If Next conv End Sub
    Листинг 1.20.
    Закрыть окно

    Text with Layout Text

    26 Text with Layout Text with Layout Конвертор может открывать и сохранять файлы MS-DOS Text with Layout MS-DOS Text with Layout Конвертор может открывать и сохранять файлы WordPerfect 5.x for Windows WrdPrfctWin WordPerfect 5.1 for DOS WrdPrfctDOS51
    Листинг 1.21.
    Закрыть окно

    и окружения Dim env As

    Public Sub WorkWithSystem() 'Получение характеристик компьютера и окружения Dim env As System Set env = Application.System 'Debug.Print "Тип компьютера - ", env.ComputerType Debug.Print "Тип процессора - ", env.ProcessorType Debug.Print "Операционная система - ", env.OperatingSystem Debug.Print "Язык - ", env.LanguageDesignation Debug.Print "Свободного дискового пространства - ", env.FreeDiskSpace Debug.Print "Курсор - ", env.Cursor
    End Sub
    Листинг 1.22.
    Закрыть окно

    Pentium Операционная система

    Тип процессора - Pentium Операционная система - Windows NT Язык - Russian (Russia) Свободного дискового пространства - 807567360 Курсор - 2
    Листинг 1.23.
    Закрыть окно

    с задачами Dim Tsk As

    Public Sub WorkWithTasks() 'Работа с задачами Dim Tsk As Task Debug.Print Application.Tasks.Count For Each Tsk In Application.Tasks Debug.Print Tsk.Name Next Tsk
    End Sub
    Листинг 1.24.
    Закрыть окно

    Microsoft Agent Microsoft Office

    65 Microsoft Agent Microsoft Office Shortcut Bar Menu Parent Window NetDDE Agent Edit Microsoft Visual Basic - DocOne [running] - [Examples (Code)] Ch1 - Microsoft Word Run Sub/UserForm View DocOne - Microsoft Word Edit
    Transmission window MarshalingWindow OLEChannelWnd MarshalingWindow Microsoft Outlook Program Manager
    Листинг 1.25.
    Закрыть окно

    Метод Display ведет диалог, не

    Public Sub WorkWithDialogs() Dim dlg As Dialog 'Открытие документа в диалоге с пользователем 'Метод Show ведет диалог и открывает документ Dialogs(wdDialogFileOpen). Show
    ' Метод Display ведет диалог, не открывая документа, 'но позволяя получить имя файла. Set dlg = Dialogs (wdDialogFileOpen) If dlg. Display = -1 Then 'нажата кнопка Open Documents.Open FileName:=dlg.Name End If End Sub
    Листинг 1.26.
    Закрыть окно

    В документе DocThree создаются разделы

    Public Sub WorkWithSections() ' В документе DocThree создаются разделы документа 'Вначале документ не имеет разделов Dim sect As Section Dim myr As Range Documents("Docthree").Activate With ActiveDocument Debug.Print "В документе разделов - ", .Sections.Count 'Выделение последнего раздела .Sections(.Sections.Count).Range.Select Debug.Print "Абзацев в разделе - ", Selection.Paragraphs.Count Debug.Print "Предложений в разделе - ", Selection.Sentences.Count Debug.Print "Слов в разделе - ", Selection.Words.Count Debug.Print "Символов в разделе - ", Selection.Characters.Count
    'Добавление разделов Set myr = .Paragraphs(11).Range myr.Select myr.InsertBreak wdSectionBreakNextPage
    Set myr = .Paragraphs(18).Range .Sections.Add myr, wdSectionEvenPage
    Set myr = .Paragraphs(29).Range .Sections.Add myr
    .Sections.Add 'Повторная печать после создания новых разделов Debug.Print "В документе разделов - ", .Sections.Count 'Выделение первого раздела Set sect = .Sections(1) Debug.Print "Абзацев в разделе - ", sect.Range.Paragraphs.Count Debug.Print "Предложений в разделе - ", sect.Range.Sentences.Count Debug.Print "Слов в разделе - ", sect.Range.Words.Count Debug.Print "Символов в разделе - ", sect.Range.Characters.Count End With End Sub
    Листинг 1.27.
    Закрыть окно

    с поддокументами Dim DocPath As

    Public Sub WorkWithSubDoc() 'Работа с поддокументами Dim DocPath As String Dim myr As Range 'Открываем и активизируем документ DocThree DocPath = Documents("DocOne").Path Documents.Open (DocPath & "\Docthree") Documents("Docthree").Activate With ActiveDocument Debug.Print "Число поддокументов =", .Subdocuments.Count If .Subdocuments.Count = 0 Then If .Sections.Count = 1 Then 'Выделение разделов WorkWithSections End If 'Выделение поддокумента, начиная с третьего раздела и до последнего Set myr = .Range(Start:=.Sections(3).Range.Start, _ End:=.Sections(.Sections.Count).Range.End) .Subdocuments.AddFromRange myr Debug.Print "Теперь число поддокументов =", .Subdocuments.Count End If End With
    End Sub
    Листинг 1.28.
    Закрыть окно

    с таблицами Dim DocPath As

    Public Sub WorkWithTables() 'Работа с таблицами Dim DocPath As String Dim myr As Range Dim ToF As TableOfFigures Dim MyTable As Table Dim i As Integer, j As Integer
    'Открываем и активизируем документ DocThree DocPath = Documents("DocOne").Path Documents.Open (DocPath & "\Docthree") Documents("Docthree").Activate
    With ActiveDocument 'Создание оглавления - Table of Contents .TablesOfContents.Add .Range(Start:=0, End:=0)
    'Создание обычной таблицы myTable в конце документа Set MyTable = .Tables.Add(Range:=.Paragraphs.Last.Range, _ NumRows:=2, NumColumns:=3) 'Заполнение таблицы For i = 1 To MyTable.Rows.Count For j = 1 To MyTable.Columns.Count MyTable.Cell(i, j).Range.InsertAfter i + j Next j Next i End With 'Вызов процедуры работы с таблицами ссылок WorkWithTablesOfFigures End Sub
    Листинг 1.29.
    Закрыть окно

    Dim Item As CaptionLabel With

    Public Sub AddCaptions() 'Работа с коллекцией заголовков
    Dim Item As CaptionLabel With CaptionLabels Debug.Print .Count 'Добавление трех заголовков .Add "Мой Рисунок" .Add "Диаграмма Excel" .Add "Мой Пример" Debug.Print .Count For Each Item In CaptionLabels Debug.Print Item .Name Next Item 'Удаление последнего заголовка .item(.Count).Delete
    End With End Sub
    Листинг 1.3.
    Закрыть окно

    В этой процедуре демонстрируется работа

    Sub WorkWithDrawingTable() ' В этой процедуре демонстрируется работа 'с рисованной таблицей Менделеева Documents("ExampleOfTable").Activate Dim DrawTable As Table Set DrawTable = ActiveDocument.Tables(1) With DrawTable Debug.Print "Столбцов - ", .Columns.Count Debug.Print "Строк - ", .Rows.Count Debug.Print .Cell(5, 1).Range.Text 'Усложняем конфигурацию .Cell(4, 5).Split 2, 3 End With End Sub
    Листинг 1.30.
    Закрыть окно

    с таблицами ссылок на иллюстрации

    Public Sub WorkWithTablesOfFigures() 'Работа с таблицами ссылок на иллюстрации документа Dim DocPath As String Dim myr As Range Dim ToF As TableOfFigures Dim capt As CaptionLabel 'Открываем и активизируем документ DocThree DocPath = Documents("DocOne").Path Documents.Open (DocPath & "\Docthree") Documents("Docthree").Activate
    With ActiveDocument
    Set myr = Selection.Range myr.Select 'Создаем таблицы ссылок на графики и таблицы 'Оба заголовка должны быть элементами коллекции CaptionLabels .TablesOfFigures.Add Range:=myr, Caption:="График" .TablesOfFigures.Add Range:=myr, Caption:="Table"
    For Each ToF In .TablesOfFigures Debug.Print ToF.Caption Next ToF For Each capt In Application.CaptionLabels Debug.Print capt.Name Next capt End With End Sub
    Листинг 1.31.
    Закрыть окно

    и InlineShapes Dim Item As

    Public Sub AddTwoShapes() 'добавляются рисунки в коллекцию TableOfFiguress и InlineShapes Dim Item As AutoCaption Dim MyPath As String Documents("DocOne").Activate MyPath = ActiveDocument.Path 'Отключим вставку автозаголовока для рисунков Set Item = Word.Application.AutoCaptions("Microsoft Word Picture") item.AutoInsert = False
    With ActiveDocument 'Рисунок добавляется в коллекцию TableOfFiguress .Shapes.AddPicture FileName:=MyPath & "\cat.gif"
    'Рисунок добавляется в коллекцию InlineShapes 'привязывается к первому параграфу документа. .InlineShapes.AddPicture FileName:=MyPath & "\mouse.bmp", _ Range:=.Paragraphs.First.Range End With End Sub
    Листинг 1.32.
    Закрыть окно

    Создание группы объектов TableOfFigures разных

    Sub GroupOfShapes() ' Создание группы объектов TableOfFigures разных типов 'Создание объекта (коллекции) TableOfFiguresRange 'Группирование объектов Dim SR As TableOfFiguresRange, SH As TableOfFigures
    With ActiveDocument.shapes 'Добавляем текстовое окно. Координаты Left-Top-Width-Height относительно якоря .AddTextbox(msoTextOrientationHorizontal, 220, _ 40, 120, 30).Select Selection.ShapeRange.Name = "Parts" Selection.ShapeRange.TextFrame.TextRange.Select Selection.Collapse Selection.TypeText Text:="Части документа" Selection.ParagraphFormat.Alignment = wdAlignParagraphCenter 'Добавляем стрелку .AddLine(280, 70, 280, 100).Select Selection.ShapeRange.Name = "Ar1" Selection.ShapeRange.Line.EndArrowheadStyle = msoArrowheadTriangle 'Добавляем линию .AddLine(120, 100, 440, 100).Select Selection.ShapeRange.Name = "Lin1" 'Добавляем стрелку .AddLine(120, 100, 120, 130).Select Selection.ShapeRange.Name = "Ar2" Selection.ShapeRange.Line.EndArrowheadStyle = msoArrowheadTriangle 'Добавляем стрелку .AddLine(440, 100, 440, 130).Select Selection.ShapeRange.Name = "Ar3" Selection.ShapeRange.Line.EndArrowheadStyle = msoArrowheadTriangle 'Добавляем текстовое окно .AddTextbox(msoTextOrientationHorizontal, 80, _ 130, 120, 30).Select Selection.ShapeRange.Name = "Part1" Selection.ShapeRange.TextFrame.TextRange.Select Selection.Collapse Selection.TypeText Text:="Рисунки" 'Добавляем текстовое окно .AddTextbox(msoTextOrientationHorizontal, 400, _ 130, 120, 30).Select Selection.ShapeRange.Name = "Part2" Selection.ShapeRange.TextFrame.TextRange.Select Selection.Collapse Selection.TypeText Text:="Таблицы"
    'Группирование объектов Set SR = .Range(Array("Parts", "Ar1", "Lin1", _ "Ar2", "Ar3", "Part1", "Part2")) SR.Select HowManyShapes Set SH = SR.Group SH.Name = "Fig.1" SH.Select HowManyShapes End With
    End Sub
    Листинг 1.33.
    Закрыть окно

    Dim SR As TableOfFiguresRange Dim

    Public Sub HowManyShapes() Dim SR As TableOfFiguresRange Dim SH As TableOfFigures Set SR = Selection.ShapeRange For Each SH In SR Debug.Print SH.Name Next SH Debug.Print Selection.ShapeRange.Count End Sub
    Листинг 1.34.
    Закрыть окно

    Parts Ar1 Lin1 Ar2 Ar3

    Parts Ar1 Lin1 Ar2 Ar3 Part1 Part2 7 Fig.1 1
    Листинг 1.35.
    Закрыть окно

    Открываем документ DocTwo Dim MyPath

    Public Sub WorkWithLists() 'работа со списками ' Открываем документ DocTwo Dim MyPath As String Dim myRange As Range MyPath = Documents("DocOne").Path 'должен быть открыт Documents.Open MyPath & "\DocTwo.doc" Documents("DocTwo").Activate With ActiveDocument Debug.Print "Списков в документе - ", .Lists.Count Debug.Print "Они занимают -", .ListParagraphs.Count, " абзацев" 'Создаем новый список Set myRange = .Range(Start:=.Paragraphs(3).Range.Start, _ End:=.Paragraphs(6).Range.End) myRange. ListFormat.ApplyBulletDefault Debug.Print "Теперь списков -", .Lists.Count Debug.Print "Они занимают - ", .ListParagraphs.Count, "абзацев" 'Повторное применение отменяет форматирование myRange. ListFormat.ApplyBulletDefault Debug.Print "Теперь списков -", .Lists.Count Debug.Print "Они занимают - ", .ListParagraphs.Count, "абзацев"
    End With End Sub
    Листинг 1.36.
    Закрыть окно

    абзацев Теперь списков

    Списков в документе - 1 Они занимают - 3 абзацев Теперь списков - 2 Они занимают - 7 абзацев Теперь списков - 1 Они занимают - 3 абзацев
    Листинг 1.37.
    Закрыть окно

    Открываем документ DocTwo Dim MyPath

    Public Sub WorkWithComments() 'работа с комментариями, сносками ' Открываем документ DocTwo Dim MyPath As String Dim myRange As Range Dim Fnote As Footnote, Enote As Endnote MyPath = Documents("DocOne").Path 'DocOne должен быть открыт Documents.Open MyPath & "\DocTwo.doc" Documents("DocTwo").Activate With ActiveDocument Set myRange = .Sections(2).Range.Paragraphs(2).Range .Comments.Add myRange, "Программный проект этого документа" _ & vbCrLf & " содержит примеры главы 1" .Comments(1).Author = "Владимир Биллиг" 'Показ комментария ActiveWindow.View.SplitSpecial = wdPaneComments .Comments. ShowBy = "Владимир Биллиг"
    'Передвигается объект Range и устанавливаются сноски: 'подстраничная и конечная myRange.Move Unit:=wdParagraph, Count:=1 .Footnotes.Add Range:=myRange, _ Text:="документ DocTwo используется для экспериментов." myRange.Move Unit:=wdParagraph, Count:=1 'нумерация конечных сносок с начала страницы '.Endnotes.NumberingRule = wdRestartPage .Endnotes.Add Range:=myRange, _ Text:="документ DocThree используется для экспериментов." 'Печать сносок For Each Fnote In .Footnotes Debug.Print Fnote.Range Next Fnote For Each Enote In .Endnotes Debug.Print Enote.Range Next Enote
    End With End Sub
    Листинг 1.38.
    Закрыть окно

    Открываем документ DocTwo Dim MyPath

    Public Sub WorkWith Revisions() 'работа с исправлениями ' Открываем документ DocTwo Dim MyPath As String Dim MyRange As Range Dim Revis As Revision 'DocOne должен быть открыт MyPath = Documents("DocOne").Path Documents.Open MyPath & "\DocTwo.doc" Documents("DocTwo").Activate With ActiveDocument 'Работа с исправлениями . Show Revisions = True 'Удаляем все имеющиеся исправления . Revisions.RejectAll
    'Добавляем новый абзац .Paragraphs.Add .Paragraphs.Last.Range.Text = "В книгах для программистов" _ & " тексты программ играют важную роль" .Paragraphs.Last.Range.Select 'Вводим исправления в последний абзац (автор Fooler) .Track Revisions = True Application.UserName = "Fooler" Selection.Range.Text = "В книгах для программистов" _ & " тексты программ не играют особой роли," _ & " а лишь усложняют понимание" 'Добавляем новый абзац .Track Revisions = False .Paragraphs.Add .Paragraphs.Last.Range.Text = "В книгах для программистов" _ & " тексты программ играют важную роль." .Paragraphs.Last.Range.Select 'Вводим исправления в последний абзац (автор Thinker) .Track Revisions = True Application.UserName = "Thinker" Selection.Range.Text = "В книгах для программистов" _ & " тексты программ весьма полезны," _ & " если только это хорошие программы."
    For Each Revis In . Revisions Debug.Print Revis.Author, Revis.Date, Revis.Range.Text If Revis.Author = "Fooler" Then Revis.Reject ElseIf Revis.Author = "Thinker" Then Revis.Accept End If Next Revis Debug.Print . Revisions.Count .Track Revisions = False . Show Revisions = False Application.UserName = "Vladimir Billig" End With
    End Sub
    Листинг 1.39.
    Закрыть окно

    Figure Table Equation Мой

    3 6 Figure Table Equation Мой Рисунок Диаграмма Excel Мой Пример
    Листинг 1.4.
    Закрыть окно

    с рисунком Dim MyPath As

    Public Sub CreateBookmarks() 'Создание закладок 'Создадим закладку в документе DocOne и свяжем ее с рисунком Dim MyPath As String Documents("DocOne").Activate With ActiveDocument MyPath = .Path .InlineShapes(1).Select 'рисунок мышки .Bookmarks.Add "PictureOfMouse", Selection.Range End With 'Создадим закладку в документе ExampleOfTable и свяжем ее с таблицей Documents.Open (MyPath & "\ExampleOfTable") Documents("ExampleOfTable").Activate With ActiveDocument .Tables(1).Select 'таблица Менделеева .Bookmarks.Add "ТаблицаМенделеева", Selection.Range End With End Sub
    Листинг 1.40.
    Закрыть окно

    в документе DocOne Dim MyPath

    Public Sub CreateHyperLinks() 'Создание гиперссылок 'Создадим три гиперссылки в документе DocOne Dim MyPath As String Documents("DocOne").Activate
    With ActiveDocument MyPath = .Path 'гиперссылка- элемент группового объекта TableOfFigures 'свяжем ее с закладкой этого же документа .shapes(2).GroupItems(6).TextFrame.TextRange.Select .Hyperlinks.Add Anchor:=Selection.Range, _ Address:="", SubAddress:="PictureOfMouse"
    'гиперссылка- элемент группового объекта TableOfFigures 'свяжем ее с закладкой другого документа .shapes(2).GroupItems(7).TextFrame.TextRange.Select .Hyperlinks.Add Anchor:=Selection.Range, _ Address:=MyPath & "\ExampleOfTable.doc", _ SubAddress:="ТаблицаМенделеева", _ ScreenTip:="Переход к документу," _ & " содержащему таблицу Менделеева" 'гиперссылка- объект TableOfFigures 'свяжем ее с URL .shapes(1).Select .Hyperlinks.Add Anchor:=Selection.Range, _ Address:="http://www.microsoft.ru/offext/", _ SubAddress:="", _ ScreenTip:="Переход к Web- странице" _ & " программы Office Extensions" End With 'Создадим гиперсссылку в документе ExampleOfTable 'и свяжем ее с закладкой документа DocOne Documents("ExampleOfTable").Activate With ActiveDocument 'Установка гиперссылки возврата в документ Word DocOne .Tables(1).Select 'таблица Менделеева Selection.MoveDown Selection.Expand .Hyperlinks.Add Anchor:=Selection.Range, _ Address:=MyPath & "\DocOne.doc", _ SubAddress:="PictureOfMouse", _ ScreenTip:="Возврат к документу DocOne" 'Установка гиперссылки перехода 'к именованному элементу документа Excel - BookOne Selection.MoveDown Selection.Expand .Hyperlinks.Add Anchor:=Selection.Range, _ Address:=MyPath & "\BookOne.xls", _ SubAddress:="ТаблицаПродаж", _ ScreenTip:="Переход к элементу" _ & " c именем ТаблицаПродаж документа Excel - BookOne"
    End With End Sub
    Листинг 1.41.
    Закрыть окно

    Удаляет по запросу закладки активного

    Public Sub RemoveBookmarks() ' Удаляет по запросу закладки активного документа Dim MyBM As Bookmark Dim Answer As String With ActiveDocument For Each MyBM In .Bookmarks Answer = InputBox(Prompt:="Удалить закладку? " & vbCrLf _ & "Имя закладки - " & MyBM.Name, _ Title:="Удаление закладок", Default:="Да") If Answer = "Да" Then MyBM.Delete
    Next MyBM End With End Sub
    Листинг 1.42.
    Закрыть окно

    Удаляет по запросу гиперссылки активного

    Public Sub RemoveHyperlinks() ' Удаляет по запросу гиперссылки активного документа Dim MyHL As Hyperlink Dim Answer As String,NameHL As String With ActiveDocument Debug.Print .Hyperlinks.Count For Each MyHL In .Hyperlinks 'определение объекта, с которым связана гиперссылка If MyHL.Type = msoHyperlinkRange Then NameHL = MyHL.Range.Text Else:NameHL = MyHL.Shape.Name End If Answer = InputBox(Prompt:="Удалить гиперссылку? " & vbCrLf _ & "связана с объектом - " &NameHL & vbCrLf _ & "Имя целевого документа - " & MyHL.Address & vbCrLf _ & "Имя целевого элемента - " & MyHL.SubAddress, _ Title:="Удаление гиперссылок", Default:="Да") If Answer = "Да" Then MyHL.Delete
    Next MyHL End With End Sub
    Листинг 1.43.
    Закрыть окно

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

    Public Sub FollowHyperlinks() ' Переход по запросу, следуя гиперссылке активного документа Dim MyHL As Hyperlink Dim Answer As String,NameHL As String With ActiveDocument For Each MyHL In .Hyperlinks 'определение объекта, с которым связана гиперссылка If MyHL.Type = msoHyperlinkRange Then NameHL = MyHL.Range.Text Else:NameHL = MyHL.Shape.Name End If Answer = InputBox(Prompt:="Перейти, следуя гиперссылке? " ;amp; vbCrLf _ & "связана с объектом - " &NameHL & vbCrLf _ & "Имя целевого документа - " & MyHL.Address & vbCrLf _ & "Имя целевого элемента - " & MyHL.SubAddress, _ Title:="Переход по гиперссылке", Default:="Да") If Answer = "Да" Then MyHL.Follow MsgBox ("Продолжаем работать!") Exit For End If
    Next MyHL End With End Sub
    Листинг 1.44.
    Закрыть окно

    Анализирует характеристики полей активного документа

    Public Sub FieldsAnalise() ' Анализирует характеристики полей активного документа Dim MyField As Field With ActiveDocument Debug.Print "Число полей - ", .Fields.Count For Each MyField In .Fields With MyField Debug.Print "Код поля - ", .Code, _ "Вид поля - ", .Kind, _ "Тип поля - ", .Type, _ "Результат поля - ", .Result End With Next MyField End With End Sub
    Листинг 1.45.
    Закрыть окно

    Близкие тебе люди" Вид поля

    Число полей - 12 Код поля - DATE \* MERGEFORMAT Вид поля - 2 Тип поля - 31 Результат поля - 13.12.99 Код поля - MERGEFIELD Country Вид поля - 2 Тип поля - 59 Результат поля - "Country" Код поля - MERGEFIELD City Вид поля - 2 Тип поля - 59 Результат поля - "City" Код поля - MERGEFIELD Address Вид поля - 2 Тип поля - 59 Результат поля - "Address" Код поля - MERGEFIELD FirstName Вид поля - 2 Тип поля - 59 Результат поля - "FirstName" Код поля - MERGEFIELD LastName Вид поля - 2 Тип поля - 59 Результат поля - "LastName" Код поля - MERGEFIELD HomePhone Вид поля - 2 Тип поля - 59 Результат поля - "HomePhone" Код поля - MERGEFIELD FirstName Вид поля - 2 Тип поля - 59 Результат поля - "FirstName" Код поля - FILLIN "Введите имя (имена) близких!" \d " Близкие тебе люди" Вид поля - 2 Тип поля - 39 Результат поля - Близкие тебе люди Код поля - ASK Vremya "Сколько лет не виделись?" \d "1" Вид поля - 2 Тип поля - 38 Результат поля - 2 Код поля - Ref Vremya Вид поля - 2 Тип поля - 3 Результат поля - 2 Код поля - = Vremya *365*24*60 Вид поля - 2 Тип поля - 34 Результат поля - 1051200
    Листинг 1.46.
    Закрыть окно

    Range, Type, Text, PreserveFormatting)

    Add( Range, Type, Text, PreserveFormatting)
    Листинг 1.47.
    Закрыть окно

    в начало документа Dim myRange

    Public Sub CreateFields() 'Работа с полями With ActiveDocument 'Добавление полей разного типа в начало документа Dim myRange As Range 'Установить автора документа ' .Name = "Vladimir Billig" Set myRange = .Range(Start:=0, End:=0) .Paragraphs.Add myRange .Paragraphs.Add myRange .Paragraphs.Add myRange myRange.Move Unit:=wdParagraph, Count:=-3 .Fields.Add Range:=myRange, Type:=wdFieldAuthor myRange.Move Unit:=wdParagraph, Count:=1 .Fields.Add Range:=myRange, Type:=wdFieldDate myRange.Move Unit:=wdParagraph, Count:=1 .Fields.Add Range:=myRange, Type:=wdFieldTime
    'Еще один способ добавления полей на примере 'добавления поля автора с одновременным изменением автора документа myRange.Move Unit:=wdParagraph, Count:=1 myRange.Select .Fields.Add Range:=myRange, Type:=wdFieldEmpty, _ PreserveFormatting:=False Selection.TypeText Text:="Author ""Fooler"""
    'Печать полей FieldsAnalyse 'Обновление полей .Fields.Update FieldsAnalyse End With End Sub
    Листинг 1.48.
    Закрыть окно

    Vladimir Billig Код поля

    Число полей - 4 Код поля - Author \* MERGEFORMAT Вид поля - 2 Тип поля - 17 Результат поля - Vladimir Billig Код поля - DATE \* MERGEFORMAT Вид поля - 2 Тип поля - 31 Результат поля 14.12.99 Код поля - TIME \* MERGEFORMAT Вид поля - 2 Тип поля - 32 Результат поля - 12:28 Код поля - Author "Fooler" Вид поля - 0 Тип поля --1 Результат поля - Число полей - 4 Код поля - Author \* MERGEFORMAT Вид поля - 2 Тип поля - 17 Результат поля - Vladimir Billig Код поля - DATE \* MERGEFORMAT Вид поля - 2 Тип поля - 31 Результат поля - 14.12.99 Код поля - TIME \* MERGEFORMAT Вид поля - 2 Тип поля - 32 Результат поля - 12:28 Код поля - Author "Fooler" Вид поля - 2 Тип поля - 17 Результат поля - Fooler
    Листинг 1.49.
    Закрыть окно

    Вставка заголовков With ActiveDocument

    Public Sub InsertLabelInDoc() 'Вставка заголовка в текст документа ' Вставка заголовков With ActiveDocument .Paragraphs.Add .Paragraphs.Last.Range.Select Selection.InsertCaption Label:="Диаграмма Excel" .Paragraphs.Add .Paragraphs.Last.Range.Select Selection.InsertCaption Label:=CaptionLabels(CaptionLabels.Count) End With End Sub
    Листинг 1.5.
    Закрыть окно

    Анализ возможных типов фрагментов акивного

    Public Sub WorkWithStory() 'Работа с фрагментами (story) ' Анализ возможных типов фрагментов акивного докуммента Dim curstory As Range With ActiveDocument For Each curstory In .StoryRanges Select Case curstory.StoryType Case wdMainTextStory Debug.Print "Начало текста:", curstory.Paragraphs(1).Range.Text Case wdCommentsStory Debug.Print "Комментарии:", curstory.Text Case wdEndnotesStory Debug.Print "Концевые ссылки:", curstory.Text Case wdFootnotesStory Debug.Print "Подстраничные ссылки:", curstory.Text Case Else Debug.Print "Фрагмент другого типа:", curstory.Text End Select Next curstory End With End Sub
    Листинг 1.50.
    Закрыть окно

    Начало текста: Vladimir Billig Подстраничные

    Начало текста: Vladimir Billig Подстраничные ссылки: документ DocTwo используется для экспериментов. Концевые ссылки: документ DocThree используется для экспериментов. Комментарии: Page: 2 Программный проект этого документа содержит примеры главы 1
    Листинг 1.51.
    Закрыть окно

    Name As String,

    Function Add( Name As String, [Value]) As Variable
    Листинг 1.52.
    Закрыть окно

    Name As String) As Boolean

    Public Sub CreateVar() 'Создание переменных - хранителей информации With ActiveDocument.Variables If Not ExistVar("Counter") Then 'Добавляем переменную .AddName:="Counter", Value:=0 End If End With
    End Sub
    Public Function ExistVar( Name As String) As Boolean 'Определяет наличие переменнойName в коллекции Variables Dim MyVar As Variable ExistVar = False For Each MyVar In ActiveDocument.Variables If MyVar.Name =Name Then ExistVar = True: Exit For End If Next MyVar End Function
    Листинг 1.53.
    Закрыть окно

    Счетчик Counter может быть использован

    Public Sub CheckCounter() Const Limit = 10 ' Счетчик Counter может быть использован в любой процедуре, 'позволяя следить за числом ее выполнения With ActiveDocument If .Variables("Counter") > Limit Then 'Исчерпан лимит нормальной работы демо-версии Call MsgBox("Исчерпан лимит работы демо-версии", _ vbCritical, "Конец работы!") Else ' продолжаем нормальную работу Dim myLocal As Integer 'Локальные переменные могут работать с глобальным счетчиком myLocal = .Variables("Counter") Debug.Print "Счетчик = "; myLocal
    'В конце работы увеличиваем значение счетчика myLocal = myLocal + 1 .Variables("Counter") = myLocal End If End With End Sub
    Листинг 1.54.
    Закрыть окно

    Пример ActiveDocument.Range.Sections(1)

    ActiveDocument.Range.Sections(1).Range.Paragraphs(1).Range.Sentences(1).Words(1).Characters(1)
    Листинг 1.55.
    Закрыть окно

    Пример Selection.Sections(1)

    Selection.Sections(1).Range.Select Selection.Paragraphs(1).Range.Select Selection.Sentences(1).Select Selection.Words(1).Select
    Листинг 1.56.
    Закрыть окно

    Dim myRange As Range, myRange1

    Dim myRange As Range, myRange1 As Range With ActiveDocument Set myRange = .Range(Start:=.Sections(2).Range.Paragraphs(3).Range.Start, _ End:=.Sections(3).Range.Paragraphs(5).Range.End) Set myRange1 = .Sections(3).Range.Paragraphs(1).Range End With
    Листинг 1.57.
    Закрыть окно

    Пример myRange.Start = myRange.End

    myRange.Start = myRange.End
    Листинг 1.58.
    Закрыть окно

    Set myRange

    Set myRange = ActiveDocument.Paragraphs(1).Range myRange.Collapse Direction:=wdCollapseEnd myRange.MoveEnd Unit:=wdCharacter, Count:=-1
    Листинг 1.59.
    Закрыть окно

    Диаграмма Excel

    Диаграмма Excel 1 Диаграмма Excel 2
    Листинг 1.6.
    Закрыть окно

    Пример ctiveDocument.Paragraphs(1).Range.Move

    ActiveDocument.Paragraphs(1).Range.Move
    Листинг 1.60.
    Закрыть окно

    Удаляются первые три слова из

    'Удаляется текст в области объекта myRange myRange.Delete ' Удаляются первые три слова из области myRange1 myRange1.Delete Unit:= wdWord, Count :=3 'Область стягивается в точку myRange1.Collapse Direction := wdCollapseStart 'Удаляются три первых слова из области, предшествующей myRange1 myRange1.Delete Unit:= wdWord, Count :=-3
    Листинг 1.61.
    Закрыть окно

    Dim myRange As Range Set

    Dim myRange As Range Set myRange = ActiveDocument.Range(Start:=0, End:=0) myRange.Text = "дорогой " myRange.InsertBefore "Мой " myRange.InsertAfter "друг! " myRange.InsertParagraphAfter
    Листинг 1.62.
    Закрыть окно

    Set myRange

    Set myRange = ActiveDocument.Range myRange.InsertParagraph
    Листинг 1.63.
    Закрыть окно

    Dim myRange As Range Dim

    Public Sub test1() Dim myRange As Range Dim myPath As String
    With ActiveDocument myPath = .Path Set myRange = .Range(Start:=0, End:=0) myRange.InsertFile myPath & "\DocTwo.doc" End With End Sub
    Листинг 1.64.
    Закрыть окно

    Dim myRange As Range Set

    .Public Sub test1() Dim myRange As Range Set myRange = ActiveDocument.Paragraphs(5).Range myRange.Select 'Добавляем новый абзац myRange.InsertAfter "New Text" myRange.InsertParagraphAfter 'Выделяем и затем вырезаем добавленный абзац и помещаем его в буфер myRange.MoveStart Unit:=wdParagraph myRange.Select Selection.Cut 'Добавляем новый абзац Selection.InsertAfter "Новый текст!" Selection.InsertParagraphAfter 'Добавляем абзац из буфера Selection.Collapse Direction:=wdCollapseEnd Selection.Paste End Sub
    Листинг 1.65.
    Закрыть окно

    Копирование рисунка через буфер Dim

    Public Sub CopyImage() ' Копирование рисунка через буфер Dim MyRange As Range Set MyRange = Documents("DocOne").Paragraphs(1).Range With MyRange 'Первый абзац этого документа содержит рисунок ' - элемент коллекции TableOfFiguress.Выделяем рисунок .MoveEnd Unit:=wdCharacter, Count:=-7 .Select .Copy End With Set MyRange = ActiveDocument.Paragraphs(6).Range MyRange.Select Selection.Collapse Direction:=wdCollapseEnd Selection.Paste
    End Sub
    Листинг 1.66.
    Закрыть окно

    Класс EventsOfApp Public WithEvents AppEv

    Option Explicit ' Класс EventsOfApp Public WithEvents AppEv As Word.Application
    Листинг 1.67.
    Закрыть окно

    ByVal Doc As Document) MsgBox

    Private Sub AppEv_DocumentOpen( ByVal Doc As Document) MsgBox ("Hi " & Doc.Name) End Sub
    Private Sub AppEv_DocumentChange() Const Msg1 = "Вы переключились на работу с новым документом!" Call MsgBox(Msg1 & vbCrLf & ActiveDocument.Name, vbInformation, "Окно информации!") End Sub
    Private Sub AppEv_DocumentBeforeClose(ByVal Doc As Document, Cancel As Boolean) MsgBox ("Вы закрываете документ " & Doc.Name) DocQuit End Sub
    Листинг 1.68.
    Закрыть окно

    Public App1 As New EventsOfApp

    Public App1 As New EventsOfApp Public Sub OnEvents() 'Определение объекта Word.Application with Events 'Связывание Set App1.AppEv = Word.Application 'Теперь начинают работать обработчики событий у Word.Application End Sub Public Sub DocQuit() With ActiveDocument If ExistVar("CounterDoc") Then MsgBox "Число открытий документа " & .Name & vbCrLf & _ .Variables("CounterDoc"), VBE xclamation, "Число открытий документа!" Else MsgBox "У документа " & .Name _ & " нет счетчика числа открытий", VBE xclamation, "Число открытий документа!" End If End With End Sub
    Листинг 1.69.
    Закрыть окно

    с коллекцией автозаголовков Dim Item

    Public Sub >WorkWithAutoLabels() 'Работа с коллекцией автозаголовков Dim Item As AutoCaption
    Debug.Print AutoCaptions.Count For Each Item In AutoCaptions 'Включение автоматической вставки заголовка item.AutoInsert = True Debug.Print Item .Name Next Item
    End Sub
    Листинг 1.7.
    Закрыть окно

    Связывание объекта Application

    Sub AutoOpen() ' Связывание объекта Application с событиями Set App1.AppEv = Application End Sub
    Листинг 1.70.
    Закрыть окно

    Microsoft Word Table Adobe

    28 Microsoft Word Table Adobe Acrobat Document Видеоклип Microsoft Chat Room Диаграмма Microsoft Excel Лист Microsoft Excel MIDI-файл
    Листинг 1.8.
    Закрыть окно

    Включаются все флажки With AutoCorrect

    Public Sub WorkWithAutoCorrect() 'Работа с объектом AutoCorrect ' Включаются все флажки With AutoCorrect .CorrectInitialCaps = True .CorrectSentenceCaps = True .CorrectDays = True .CorrectCapsLock = True .ReplaceText = True .ReplaceTextFromSpellingChecker = True .CorrectKeyboardSetting = True
    'В коллекцию Entries, задающую замены при автокоррекции, 'добавляются два элемента. .Entries.AddName:="ДЕ", Value:="Диаграмма Excel" .Entries.AddName:="ГЕ", Value:="График Excel"
    End With End Sub
    Листинг 1.9.
    Закрыть окно

    Простые объекты, вложенные в объект Document

    Объектов, вложенных на верхнем уровне иерархии в объект Document, существующих в одном экземпляре и не имеющих соответственно коллекций, сравнительно немного. Вот их список:
  • Email
  • EmailAuthor
  • Envelope
  • Frameset
  • HTMLProject
  • LetterContent
  • MailMerge
  • MailMergeDataSource
  • MailMergeFields
  • PageSetup
  • LineNumbering
  • TextColumns
  • Range
  • RoutingSlip
  • VBProject
  • WebOptions

  • В Office 2000 у объекта Document появились три новых объекта в сравнении с предыдущей версией. Это объекты: Email, Frameset и WebOptions.
    Итак, только на верхнем уровне в объект Document встроено около 50 объектов, определяющих его свойства. Большинство из этих объектов устроены достаточно сложно, - почти все содержат коллекции внутри себя. Попробуем упорядочить эту "тьму" объектов.

    Проверка правильности написания текста

    Три объекта, связанные с проверкой грамматики и орфографии: Languages, Dictionaries, SpellingSuggestions, позволяют установить нужный язык, выбрать словарь, в том числе пользовательские словари, а также работать со списком слов, предлагаемых для исправления при обнаружении ошибки правописания. Команды SpellingAndGrammar и Language меню Tools предоставляют аналогичные возможности при работе с документом вручную. Новый объект LanguageSettings позволяет получить установки языкового предпочтения на разных этапах работы с приложением. Взгляните на пример, в котором показана работа с этими объектами:
    Листинг 1.14.
    (html, txt)
    Приведем результаты работы этой процедуры, определяющие мои текущие языковые установки:
    'C:\Program Files\Common Files\Microsoft Shared\Proof\MSSP_RU.LEX Русский Установлен английский язык в качестве языка инсталляции, интерфейса и справки Русский язык является одним из предпочтительных языков редактирования.
    Листинг 1.15.
    (html, txt)

    Работа с документами и класс Document

    Вспомним, что мы уже знаем о документах Word. Когда открывается приложение, создается коллекция документов Documents, содержащая открытые документы. Если приложение Word создается в момент открытия документа Word, (заметим, что объект Word.Application может быть создан и в программном проекте другого приложения, например, Excel), то в начальный момент коллекция содержит минимум один новый или ранее существовавший документ. Программно новый документ добавляется в коллекцию методом Add, а уже существующий - методом Open объекта Documents. Чтобы добраться до нужного документа в коллекции, достаточно указать его индекс - имя файла, хранящего документ, - или его порядковый номер в коллекции. Для той же цели можно использовать и метод Item, но обычно он опускается. Метод Save позволяет сохранить документ, а метод Close, сохраняя документ в файле, закрывает его и удаляет из коллекции.
    При применении метода Open обязательно указывать имя, а точнее путь к открываемому файлу. Однако задание конкретного пути всегда чревато неприятностями, поскольку при любом переносе системы местоположение файла может измениться. В одной из предыдущих процедур этой лекции WorkWithSearch я демонстрировал возможность использования свойства Path для нахождения пути к документу. Часто предпочтительнее предоставить пользователю возможность выбирать открываемый файл, хранящий документ. Для этого, конечно, можно использовать объект Dialog, при вызове методов которого открывается соответствующее диалоговое окно.
    Вот еще один пример использования уже знакомого нам объекта Dialog FileOpen:
    Public Sub WorkWithDialogs() Dim dlg As Dialog 'Открытие документа в диалоге с пользователем 'Метод Show ведет диалог и открывает документ Dialogs(wdDialogFileOpen). Show
    'Метод Display ведет диалог, не открывая документа, 'но позволяя получить имя файла. Set dlg = Dialogs (wdDialogFileOpen) If dlg. Display = -1 Then 'нажата кнопка Open Documents.Open FileName:=dlg.Name End If End Sub
    Листинг 1.26.
    (html, txt)
    Теперь подробнее рассмотрим свойства и методы документа - объекта класса Document. Это основной объект, свойства, события и методы которого следует знать основательно. Объект Document не менее сложен, чем объект Application.

    Работа с фрагментами

    StoryRanges(Range) - эта коллекция представляет совокупность частей документа, называемых фрагментами (Story). Количество различных фрагментов документа фиксировано. Нельзя добавлять элементы в эту коллекцию обычным способом, используя метод Add. Фрагменты появляются в коллекции, когда создается соответствующая часть документа. В этот момент определяется и тип фрагмента. Фрагменты имеют тип, задаваемый константами из перечисления wdStoryType. Главный фрагмент, конечно, - текст документа, тип которого задается константой wdMainTextStory. Фрагментами других типов являются комментарии, ссылки, колонтитулы. Заметьте: сам фрагмент является объектом Range. Так что благодаря фрагментам можно, например, работать с коллекцией комментариев, как с единой областью.
    Приведем пример, где анализируются типы фрагментов активного документа:
    Листинг 1.50.
    (html, txt)
    Тестовый документ состоит из фрагментов четырех типов, так как он, кроме текста, содержит комментарии и два типа ссылок. Вот результаты отладочной печати:
    Начало текста: Vladimir Billig Подстраничные ссылки: документ DocTwo используется для экспериментов. Концевые ссылки: документ DocThree используется для экспериментов. Комментарии: Page: 2 Программный проект этого документа содержит примеры главы 1
    Листинг 1.51.
    (html, txt)

    Работа с полями документа

    Поля (коллекция Fields) используются в документах Word достаточно широко и играют множество самых разных ролей. Поэтому стоит поговорить о них подробнее. В зависимости от назначения поля оно относится к одной из 9 возможных категорий. Нет смысла и возможности заниматься полным анализом этих категорий, но некоторые основные роли, которые играют поля, я перечислю:
  • Поля могут хранить в документе некоторую обновляемую информацию. Такими полями являются поля Date и Time из категории Date and Time, хранящие текущую дату и текущее время. В полях Author, FileName, KeyWords, NumPages и других полях из категории Document Information содержится подробная информация о документе авторе документа, файле, в котором он находится, числе занимаемых страниц. В полях UserName и UserAddress из категории User Information хранится информация о фамилии и адресе владельца компьютера, используемая по умолчанию для создания поля Author документа, для создания обратного адреса по умолчанию при работе с конвертами. Эта роль полей достаточно понятна. Ясно, как программно и вручную можно работать с такими полями. Заметим, что значения некоторых из упомянутых полей меняются автоматически, например, время, дата, число страниц документа (число страниц документа - 57, число символов - 149609, дата - 01.07.2006, время - 9:27 )
    Я только что вставил в текст, указанный в скобках, ряд полей и получил текущую информацию. За время печати этого замечания значения некоторых из этих полей число символов документа и время изменились.
  • Поля играют важную роль при создании документов с помощью слияния. Зачастую возникает необходимость создания группы однотипных документов, отличающихся лишь небольшими деталями. Типичным примером такого рода является группа рассылаемых писем, отличающихся адресом, названием компании и другими деталями. В этом случае создается главный документ, содержащий шаблон письма с полями и документ, хранящий источник данных. Поля главного документа задают переменную часть письма адрес, название компании и другие данные. Значения этих данных хранятся в источнике данных, который может быть таблицей Word, Access или Excel. Слияние главного документа с одной записью источника данных (строкой таблицы) приводит к появлению нового документа (письма). В слиянии могут участвовать как все записи источника данных, так и только часть из них, выбранная по запросу. В результате возникает необходимая совокупность писем. Имена полей в этом случае могут быть произвольными и представляют имена столбцов в таблице, задающей источник данных. Помимо этих полей в главном документе могут встречаться и поля Word из тех 9 категорий, о которых я уже говорил. В ситуациях слияния часто используются такие поля как Ask и Fill-In, позволяя в момент слияния запросить и добавить в документ индивидуальную информацию, не хранящуюся в записях источника данных. Поле Fill-In позволяет запросить данные при создании документа и сделать полученный ответ значением этого поля. Поле Ask работает аналогичным образом, за тем исключением, что ответ не становится значением поля, а связывается с некоторой создаваемой в этот момент закладкой. Затем эту закладку можно использовать в документе различными способами, принятыми для закладок. На деталях работы вручную останавливаться не буду, но пример такого главного документа приведу:

    Работа с полями документа

    Рис. 1.14.  Главный документ с полями разных типов

    Заметьте, поля в главном документе подсвечены и среди них есть как поля, заданные источником данных, так и общие для Word поля, Ask, Fill-In, Formula, Date. Вот как выглядит источник данных, с которым я проводил эксперименты:

    Работа с полями документа

    увеличить изображение
    Рис. 1.15.  Источник данных для слияния документов

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

    Работа с полями документа

    Рис. 1.16.  Документ, полученный слиянием главного документа и источника данных

  • Еще одним видом документа Word, использующим поля, является электронная форма. Необходимость в таких документах возникает, например, при пересылке анкет по электронной почте. Получатель формы заполняет поля анкеты, которые могут быть элементами управления: обычными текстовыми окнами ввода, флажками или выпадающими списками. Как поля они имеют имена: FormTextBox, FormCheckBox, FormDropDown.
  • Говоря о полях, нельзя не упомянуть о таком важном и интересном поле, как поле Formula, позволяющее организовывать вычисления в документах Word. Эти вычисления, чаще всего, организуются в ячейках таблиц Word, но могут быть вставлены и в произвольное место документа. Пример такого поля приведен в главном документе. Строятся формулы аналогично формулам Excel и могут работать как с закладками, используемыми в качестве имен переменных, так и с ячейками таблиц Word. При вычислениях можно использовать некоторые из встроенных функций, допустимых в Excel. Тема вычислений в документах Word , его таблицах заслуживает, конечно, более подробного освещения, чем эти несколько строчек. Но обо всем сказать, нам не дано.
  • Есть и другие случаи применения полей в документах Word. Например, поля автоматически создаются при создании таблиц ссылок, о которых я рассказывал ранее.


  • Я постарался кратко описать достаточно сложную тему работы с полями в документах Word. Конечно, обычно работа с полями выполняется вручную. Вряд ли, например, есть смысл создавать документ слияния программным путем. Поэтому, говоря о программировании работы с полями, я ограничусь достаточно простыми примерами. В основе программной работы с полями лежит работа с коллекцией полей Fields(Field). В следующем примере анализируются (печатаются) основные свойства объекта Field:


    Public Sub FieldsAnalise() ' Анализирует характеристики полей активного документа Dim MyField As Field With ActiveDocument Debug.Print "Число полей - ", .Fields.Count For Each MyField In .Fields With MyField Debug.Print "Код поля - ", .Code, _ "Вид поля - ", .Kind, _ "Тип поля - ", .Type, _ "Результат поля - ", .Result End With Next MyField End With End Sub

    Листинг 1.45.

    (html, txt)

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

    Листинг 1.46.

    (html, txt)

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

    Для программного создания полей используется, как обычно, метод Add коллекции Fields. Он имеет следующий синтаксис:

    Листинг 1.47.

    (html, txt)

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

    Рассмотрим теперь пример программного добавления полей в документ. Я ограничусь достаточно простой ситуацией. Добавим в начало документа три поля, задающие автора документа, дату и время:

    Листинг 1.48.

    (html, txt)

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

    Листинг 1.49.

    (html, txt)


    Add(Range, Type, Text, PreserveFormatting)

    Листинг 1.47.

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

    Рассмотрим теперь пример программного добавления полей в документ. Я ограничусь достаточно простой ситуацией. Добавим в начало документа три поля, задающие автора документа, дату и время:

    Public Sub CreateFields() 'Работа с полями With ActiveDocument 'Добавление полей разного типа в начало документа Dim myRange As Range 'Установить автора документа ' .Name = "Vladimir Billig" Set myRange = .Range(Start:=0, End:=0) .Paragraphs.Add myRange .Paragraphs.Add myRange .Paragraphs.Add myRange myRange.Move Unit:=wdParagraph, Count:=-3 .Fields.Add Range:=myRange, Type:=wdFieldAuthor myRange.Move Unit:=wdParagraph, Count:=1 .Fields.Add Range:=myRange, Type:=wdFieldDate myRange.Move Unit:=wdParagraph, Count:=1 .Fields.Add Range:=myRange, Type:=wdFieldTime

    'Еще один способ добавления полей на примере 'добавления поля автора с одновременным изменением автора документа myRange.Move Unit:=wdParagraph, Count:=1 myRange.Select .Fields.Add Range:=myRange, Type:=wdFieldEmpty, _ PreserveFormatting:=False Selection.TypeText Text:="Author ""Fooler"""

    'Печать полей FieldsAnalyse 'Обновление полей .Fields.Update FieldsAnalyse End With End Sub

    Листинг 1.48.

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

    Число полей - 4 Код поля - Author \* MERGEFORMAT Вид поля - 2 Тип поля - 17 Результат поля - Vladimir Billig Код поля - DATE \* MERGEFORMAT Вид поля - 2 Тип поля - 31 Результат поля 14.12.99 Код поля - TIME \* MERGEFORMAT Вид поля - 2 Тип поля - 32 Результат поля - 12:28 Код поля - Author "Fooler" Вид поля - 0 Тип поля --1 Результат поля - Число полей - 4 Код поля - Author \* MERGEFORMAT Вид поля - 2 Тип поля - 17 Результат поля - Vladimir Billig Код поля - DATE \* MERGEFORMAT Вид поля - 2 Тип поля - 31 Результат поля - 14.12.99 Код поля - TIME \* MERGEFORMAT Вид поля - 2 Тип поля - 32 Результат поля - 12:28 Код поля - Author "Fooler" Вид поля - 2 Тип поля - 17 Результат поля - Fooler

    Листинг 1.49.

    Работа с текстом

    Объекты Range и Selection позволяют выполнять основные операции над текстом (и не только над текстом): "выделить", "добавить", "заменить", "удалить". У наших объектов большой набор методов, позволяющих реализовать эти операции. Все рассматриваемые здесь методы принадлежат обоим объектам, если не сделана специальная оговорка.
    Выделение
    Говоря в этом параграфе о выделении, я имею в виду не применение метода Select, а выделение в более широком смысле, умение задать некоторую подобласть данной области. Выделить некоторую часть текста означает, по существу, определение объекта Range или Selection. Оба объекта задают непрерывную область, а их свойства Start и End позволяют задать начало и конец области. Меняя эти свойства, можно задать нужную область выделения. Этот основной способ выделения мы не раз демонстрировали в наших примерах:
    Dim myRange As Range, myRange1 As Range With ActiveDocument Set myRange = .Range(Start:=.Sections(2).Range.Paragraphs(3).Range.Start, _ End:=.Sections(3).Range.Paragraphs(5).Range.End) Set myRange1 = .Sections(3).Range.Paragraphs(1).Range End With
    Листинг 1.57.
    (html, txt)
    В первом случае при задании области используются параметры Start и End, во втором - задается вся область данного объекта. Область может изменяться автоматически при добавлении или удалении из нее части текста. Изменить область можно и путем ее перемещения. Этим занимается специальная группа методов перемещения Move. Прежде чем говорить о перемещении, рассмотрим сжатие области. Нередко нужна пустая область - точка вставки, параметры Start и End которой совпадают. Поэтому простейший способ сжатия - задать совпадающие значения этих параметров:
    Листинг 1.58.
    (html, txt)
    Для сжатия области можно применять специальный метод сжатия - Collapse(Direction). Область стягивается в начальную или конечную позицию. Направление сжатия задает параметр Direction, принимающий значения wdCollapseStart или wdCollapseEnd. По умолчанию область стягивается в начальную точку (значение параметра: wdCollapseStart). Если сжимается абзац, и он стягивается в конечную точку, точка вставки переносится за метку конца абзаца и устанавливается в начало следующего абзаца. Если такой эффект нежелателен, после сжатия применяется метод перемещения MoveEnd, передвигающий точку вставки назад на один символ:

    Set myRange = ActiveDocument.Paragraphs(1).Range myRange.Collapse Direction:=wdCollapseEnd myRange.MoveEnd Unit:=wdCharacter, Count:=-1

    Листинг 1.59.

    (html, txt)

    Если есть методы сжатия области, то должны быть и методы расширения области. Основным из них является метод Expand(Unit). В зависимости от значения параметра Unit область можно расширить на слово, предложение, абзац, раздел, на строку или столбец таблицы, или на всю таблицу. Для объекта Selection область можно расширить на всю строку. Для расширения области на весь фрагмент можно использовать метод WholeStory, что впрочем эквивалентно вызову метода Expand(Unit := wdStory)

    Метод Move является основным методом перемещения. Остальные методы - в той или иной степени его модификации. Метод Move(Unit, Count) сжимает область в точку, стягивая ее в начало или конец, и затем перемещает точку вставки. Параметр Unit определяет единицы перемещения, а Count - количество этих единиц и направление стягивания и перемещения (по умолчанию 1). Положительные значения этого параметра задают стягивание к концу и перемещение вперед, отрицательные - стягивание в начало и перемещение назад. Само стягивание означает перемещение на одну единицу. Метод возвращает количество единиц, на которое фактически произошло перемещение, или 0, если оно не осуществлено. Параметр Unit принимает значения wdCharacter (по умолчанию), wdWord, wdSentence, wdParagraph, wdSection, wdStory, wdCell, wdColumn, wdRow и wdTable.

    Методы перемещения на сам текст не влияют - лишь изменяют область, заданную объектами Range и Selection. Поэтому эти методы применимы только к переменным типа Range, но не к фиксированным областям. Например, запись:

    Листинг 1.60.

    (html, txt)

    не имеет эффекта, поскольку область первого абзаца - вещь неизменяемая.

    Метод Move стягивает область в точку, которая и перемещается, поэтому после его выполнения область исчезает, и остается только точка вставки. Методы MoveStart и MoveEnd перемещают начальную или конечную точку области, обычно расширяя тем самым область.

    Конечно, для перемещения по тексту документу есть много различных возможностей, кроме группы методов Move. Стоит упомянуть группу методов Next, основным из которых является метод Next(Unit,Count). Основное отличие от метода Move с теми же параметрами состоит в том, что метод Next возвращает сам объект Range, в отличие от метода Move, возвращающего число символов, на которое произошло перемещение. Напомним также о тех возможностях перемещения, которыми обладают объекты Browser и Hyperlink, напомним о закладках, специально предназначенных для перехода к ним.

    Удаление текста

    Метод Delete позволяет удалить текст. Вызванный без параметров, он удаляет вызывающий его объект Range или Selection. Если он применен в форме Delete(Unit,Count), удаляется часть текста в указанной области. Параметр Unit задает единицы, но при удалении возможны только два значения: wdWord и wdCharacter. Параметр Count задает количество удаляемых единиц. Если область стянута в точку, удаляются символы перед точкой вставки или после нее в зависимости от знака параметра Count. Вот несколько примеров:


    'Удаляется текст в области объекта myRange myRange.Delete ' Удаляются первые три слова из области myRange1 myRange1.Delete Unit:= wdWord, Count :=3 'Область стягивается в точку myRange1.Collapse Direction := wdCollapseStart 'Удаляются три первых слова из области, предшествующей myRange1 myRange1.Delete Unit:= wdWord, Count :=-3

    Листинг 1.61.

    (html, txt)

    Вставка текста

    Группа методов Insert объектов Range и Selection позволяет осуществлять вставки в документ. Для вставки текста используются методы InsertBefore(Text) и InsertAfter(Text). Параметр Text типа String задает текст, вставляемый до или после области, заданной объектами Range или Selection. После вставки текста область автоматически расширяется, включая в себя добавляемый текст. Вот пример вставки нового абзаца в начало документа:

    Dim myRange As Range Set myRange = ActiveDocument.Range(Start:=0, End:=0) myRange.Text = "дорогой " myRange.InsertBefore "Мой " myRange.InsertAfter "друг! " myRange.InsertParagraphAfter

    Листинг 1.62.

    (html, txt)

    Свойство Text позволяет заменять текст в выделенной области, поэтому нет нужды вызывать метод Insert(Text), - лучше использовать свойство. Методы InsertBefore и InsertAfter безопасны, так как текст добавляется, не изменяя содержимого области. При вставке внутрь области, например, при использовании метода InsertSymbol или InsertParagraph, заменяется содержимое области. Эта вроде бы безобидная программка сотрет все содержимое документа, заменив его пустым абзацем:

    Set myRange = ActiveDocument.Range myRange.InsertParagraph

    Листинг 1.63.

    (html, txt)

    Говоря о методах вставки текста, нельзя не упомянуть о таком мощном методе, как InsertFile - он позволяет вставлять не только текст или таблицу Excel, но и целый документ, хранящийся в файле. Вот простой пример вызова метода, при котором в начало активного документа вставляется существующий документ:

    Public Sub test1() Dim myRange As Range Dim myPath As String

    With ActiveDocument myPath = .Path Set myRange = .Range(Start:=0, End:=0) myRange.InsertFile myPath & "\DocTwo.doc" End With End Sub


    Листинг 1.64.

    (html, txt)

    Работа с буфером

    Известно, как полезен буфер при работе с одним и, особенно, с несколькими документами. Объекты Range и Selection в полной мере позволяют задействовать все возможности буфера. Метод Copy, не имеющий параметров, копирует объект (содержимое области) в буфер. Метод Cut, действуя аналогично, копирует объект в буфер, заодно удаляя его. Заметьте, что в отличие от предыдущей версии, теперь метод Cut работает, как ему положено, не только копируя объект, но и удаляя его, ранее удаления не происходило, вместо этого объект, вызывавший метод стягивался в точку. Метод Paste позволяет приклеить объект, помещенный в буфер. Рассмотрим пример:

    .Public Sub test1() Dim myRange As Range Set myRange = ActiveDocument.Paragraphs(5).Range myRange.Select 'Добавляем новый абзац myRange.InsertAfter "New Text" myRange.InsertParagraphAfter 'Выделяем и затем вырезаем добавленный абзац и помещаем его в буфер myRange.MoveStart Unit:=wdParagraph myRange.Select Selection.Cut 'Добавляем новый абзац Selection.InsertAfter "Новый текст!" Selection.InsertParagraphAfter 'Добавляем абзац из буфера Selection.Collapse Direction:=wdCollapseEnd Selection.Paste End Sub

    Листинг 1.65.

    (html, txt)

    Метод Paste позволяет "вклеить" содержимое буфера в область, заданную объектами Range и Selection. Эта операция опасна, так как происходит замена, а не добавление текста. Поэтому обычно метод Paste применяется к объектам Range и Selection, стянутым в точку вставки. В выполнении этого метода есть нюансы. У объекта Range содержимое буфера включается в его область, Объект Selection остается точкой вставки, расположенной после текста, добавленного из буфера.

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


    Public Sub CopyImage() ' Копирование рисунка через буфер Dim MyRange As Range Set MyRange = Documents("DocOne").Paragraphs(1).Range With MyRange 'Первый абзац этого документа содержит рисунок ' - элемент коллекции TableOfFiguress.Выделяем рисунок .MoveEnd Unit:=wdCharacter, Count:=-7 .Select .Copy End With Set MyRange = ActiveDocument.Paragraphs(6).Range MyRange.Select Selection.Collapse Direction:=wdCollapseEnd Selection.Paste

    End Sub

    Листинг 1.66.

    (html, txt)

    Иногда в буфер копируют формат текста. Этим занимается метод CopyFormat, копирующий формат по первому символу объекта Selection. Если этот символ - метка абзаца, копируется формат абзаца. Методом CopyFormat обладает только объект Selection.

    Метод PasteFormat применяет форматирование, хранящееся в буфере к объекту Selection.

    Метод PasteSpecial позволяет явно управлять форматированием в момент вставки объекта из буфера.

    Разделы и поддокументы

    Сейчас мы обсудим более подробно две коллекции Sections и SubDocuments, задающие разделы документа и поддокументы, входящие в состав основного документа. Почти в каждом текстовом документе можно встретить символы, слова, предложения и абзацы. Что же касается более крупных единиц текста, то в разных документах они называются по-разному. Чаще всего, приходится встречаться со страницами и листами документа, но используются и такие термины как параграфы, главы, части, разделы документа. В объектной модели Word, к сожалению, нет таких объектов как Page и Pages, соответствующих таким естественным единицам текстового документа как страницы документа и их коллекции. В документе Word следующей крупной единицей после абзаца является раздел - объект класса Section. Все разделы одного документа составляют коллекцию Sections.
    Что же такое раздел, как и для чего он создается? Раздел в документе всегда можно создать руками, для чего достаточно вставить символ разрыва документа - Section break, вызвав пункт Break из меню Insert. Символ разрыва может быть разного типа, задавая следующую страницу, четную или нечетную страницу, столбец и другие виды раздела. Таким образом, можно руками разбить документ на страницы, вставляя подходящие символы разрыва в нужных местах. В этом случае разделы будут выступать в роли страниц документа. Иногда разделы создаются автоматически, например, при представлении некоторой части документа в виде нескольких столбцов. Часть документа, представленная в виде нескольких столбцов, представляет отдельный раздел.
    При программной работе разделы можно создавать двояко, используя метод InsertBreak, которым обладают объекты Range и Selection, или вызывая метод Add коллекции Sections. Метод Add(Range, Start) имеет два параметра:
  • Range задает область, начинающую новый раздел, или, что тоже, область, перед началом которой будет вставлен символ разрыва.
  • Start задает тип символа разрыва.

  • Заметьте, в методе InsertBreak указывается только второй параметр, поскольку новый раздел начинается с объекта Range (Selection), вызвавшего метод.
    Рассмотрим пример, в котором документ с именем DocThree, программно разбивается на разделы. Наше разбиение гарантирует, что соответствующие главы и параграфы документа находятся в отдельных разделах, что облегчает дальнейшую работу с ними. Вот как выглядит процедура, решающая эту задачу:

    Листинг 1.27.

    (html, txt)

    В этой процедуре показано, как происходит выделение раздела, подсчет некоторых характеристик раздела, например, подсчет числа абзацев, предложений и символов раздела. Показано, как создаются разделы документа, используя как метод InsertBreak так и метод Add коллекции Sections. Метод Add вызывается как с явно заданными параметрами, так и параметрами, задаваемыми по умолчанию.

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

    Листинг 1.28.

    (html, txt)

    Наш документ DocThree вначале не имел поддокументов. Этот документ предварительно разбивается на разделы, если это еще не было сделано, а затем в нем выделяется поддокумент, начиная с третьего раздела и кончая последним разделом документа. Метод AddFromRange класса SubDocuments создает поддокумент, выделяя из главного документа область, заданную параметром Range.

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

    Разделы и поддокументы

    Рис. 1.9.  Главный документ с выделенным поддокументом

    Рисунки и элементы управления. Объекты класса TableOfFigures

    Word предоставляет широкие возможности для появления рисунков в его документах. При работе вручную рисунки в документах появляются по-разному: во-первых, можно вставлять уже готовые рисунки в текст документа, можно также вставить элемент управления Image и связать с ним готовый рисунок, во-вторых, можно воспользоваться широким набором инструментов рисования, собранными на панели Drawing (Рисование). С помощью этих инструментов можно вставлять готовые рисованные объекты самых разных типов, начиная с прямоугольников и овалов, кончая блок-схемами, фигурными стрелками и звездами. Большие возможности представляют инструменты Curve (Кривая), FreeForm (Полилиния), Scribble (Рисованная Кривая). Чтобы добраться до них, необходимо на панели Drawing выбрать вкладку AutoShapes (АвтоФигуры), затем Lines (Линии), а затем нажать нужную кнопку. В этот момент в руках у пользователя "появляется" обычный карандаш, с помощью которого можно нарисовать любой рисунок произвольной (свободной) формы, проводя линии выбранным цветом и выбранной толщиной. После чего весь рисунок можно закрасить (залить) нужным цветом. Созданный рисунок можно вращать, растягивать или сжимать, в общем, выполнять основные операции, допустимые в графических редакторах.
    Все, что можно делать вручную, можно делать и программно, поскольку есть соответствующие объекты со своими свойствами и методами. Все рисунки, размещаемые в документе в слое "рисования", с объектной точки зрения являются объектами класса TableOfFigures или InlineShape. Но, обратите внимание, коллекции TableOfFiguress(Shape), InlineShapes(InlineShape) содержат рисунки документа, но не только их! Рисунки это только один из возможных типов объектов, хранящихся в этих коллекциях. ActiveX- и OLE-объекты, рисованные тексты, созданные средствами Word Art, также являются элементами этих коллекций. Напомню, что элементы управления, размещаемые непосредственно в документе, являются OLE- объектами, об этом я уже говорил во введении. Но, заметьте, одновременно они являются и объектами класса TableOfFigures, точнее InlineShape. Так что при размещении в документе, например, командной кнопки или элемента Image, как бы оно не выполнялось вручную или программно, в коллекции InlineShapes появится новый элемент. Объекты разных типов, находящиеся в этих коллекциях, объединяет то, что всех их можно отнести к рисованным объектам и размещаются они в документе в слое рисования.
    Коллекции TableOfFiguress и InlineShapes близки по своей природе. Как правило, при программном создании объектов многие из них можно поместить по желанию либо в коллекцию TableOfFiguress, либо в InlineShapes. Отличаются элементы этих двух коллекций тем, как они привязаны к документу - первые могут свободно перемещаться по документу, вторые жестко привязаны к заданной области документа и ведут себя подобно символам текста документа. Но, заметьте, элементы той и другой коллекции имеют метод Convert (ConvertToShape, ConvertToInlineShape), позволяющий конвертировать объект класса InlineShape в объект класса TableOfFigures и обратно. Коллекция TableOfFiguress содержит больше типов элементов, чем коллекция InlineShapes, поэтому преобразование не всегда возможно.
    Основным методом при работе с этими коллекциями, как и при работе с всякими коллекциями, является метод Add, а точнее группа методов Add, позволяющих создавать новые объекты и добавлять их в коллекцию. Рассмотрим несколько модификаций метода Add, используемых при работе с этими коллекциями:
  • Прежде всего, следует упомянуть метод Addshape, первый параметр которого задает тип добавляемого объекта. Поскольку, как я уже говорил, типов рисованных объектов достаточно много, то соответствующая константа, задающая тип, может принимать около сотни различных значений. По этой причине, чаще всего, пользуются не этим общим методом создания TableOfFigures-объектов, а частными методами, позволяющими создавать TableOfFigures-объекты определенного типа.
  • Вот методы, позволяющие создавать TableOfFigures-объекты, аналогичные тем, что создаются вручную при работе с инструментами панели Drawing: AddCallout , AddCurve, AddLine, AddPolyline, BuildFreeForm. Последний из этих методов соответствует двум уже упоминавшимся инструментам FreeForm и Scribble. При работе с этим методом создается объект класса FreeForm, который затем преобразуется в объект класса TableOfFigures. Метод AddOleObject используется для создания OLE-объектов, в частности для создания элементов управления.
  • Метод AddOleControl используется для создания ActiveX объектов.
  • Метод AddTextBox позволяет создать текстовые окна.
  • Метод AddPicture позволяет добавлять рисунки в документ.
  • Метод AddTextEffect позволяет создавать художественные надписи, так как это делает Word Art.


  • Word предоставляет широкие возможности для появления рисунков в его документах. При работе вручную рисунки в документах появляются по-разному: во-первых, можно вставлять уже готовые рисунки в текст документа, можно также вставить элемент управления Image и связать с ним готовый рисунок, во-вторых, можно воспользоваться широким набором инструментов рисования, собранными на панели Drawing (Рисование). С помощью этих инструментов можно вставлять готовые рисованные объекты самых разных типов, начиная с прямоугольников и овалов, кончая блок-схемами, фигурными стрелками и звездами. Большие возможности представляют инструменты Curve (Кривая), FreeForm (Полилиния), Scribble (Рисованная Кривая). Чтобы добраться до них, необходимо на панели Drawing выбрать вкладку AutoShapes (АвтоФигуры), затем Lines (Линии), а затем нажать нужную кнопку. В этот момент в руках у пользователя "появляется" обычный карандаш, с помощью которого можно нарисовать любой рисунок произвольной (свободной) формы, проводя линии выбранным цветом и выбранной толщиной. После чего весь рисунок можно закрасить (залить) нужным цветом. Созданный рисунок можно вращать, растягивать или сжимать, в общем, выполнять основные операции, допустимые в графических редакторах.
    Все, что можно делать вручную, можно делать и программно, поскольку есть соответствующие объекты со своими свойствами и методами. Все рисунки, размещаемые в документе в слое "рисования", с объектной точки зрения являются объектами класса TableOfFigures или InlineShape. Но, обратите внимание, коллекции TableOfFiguress(Shape), InlineShapes(InlineShape) содержат рисунки документа, но не только их! Рисунки это только один из возможных типов объектов, хранящихся в этих коллекциях. ActiveX- и OLE-объекты, рисованные тексты, созданные средствами Word Art, также являются элементами этих коллекций. Напомню, что элементы управления, размещаемые непосредственно в документе, являются OLE- объектами, об этом я уже говорил во введении. Но, заметьте, одновременно они являются и объектами класса TableOfFigures, точнее InlineShape. Так что при размещении в документе, например, командной кнопки или элемента Image, как бы оно не выполнялось вручную или программно, в коллекции InlineShapes появится новый элемент. Объекты разных типов, находящиеся в этих коллекциях, объединяет то, что всех их можно отнести к рисованным объектам и размещаются они в документе в слое рисования.
    Коллекции TableOfFiguress и InlineShapes близки по своей природе. Как правило, при программном создании объектов многие из них можно поместить по желанию либо в коллекцию TableOfFiguress, либо в InlineShapes. Отличаются элементы этих двух коллекций тем, как они привязаны к документу - первые могут свободно перемещаться по документу, вторые жестко привязаны к заданной области документа и ведут себя подобно символам текста документа. Но, заметьте, элементы той и другой коллекции имеют метод Convert (ConvertToShape, ConvertToInlineShape), позволяющий конвертировать объект класса InlineShape в объект класса TableOfFigures и обратно. Коллекция TableOfFiguress содержит больше типов элементов, чем коллекция InlineShapes, поэтому преобразование не всегда возможно.
    Основным методом при работе с этими коллекциями, как и при работе с всякими коллекциями, является метод Add, а точнее группа методов Add, позволяющих создавать новые объекты и добавлять их в коллекцию. Рассмотрим несколько модификаций метода Add, используемых при работе с этими коллекциями:
  • Прежде всего, следует упомянуть метод Addshape, первый параметр которого задает тип добавляемого объекта. Поскольку, как я уже говорил, типов рисованных объектов достаточно много, то соответствующая константа, задающая тип, может принимать около сотни различных значений. По этой причине, чаще всего, пользуются не этим общим методом создания TableOfFigures-объектов, а частными методами, позволяющими создавать TableOfFigures-объекты определенного типа.
  • Вот методы, позволяющие создавать TableOfFigures-объекты, аналогичные тем, что создаются вручную при работе с инструментами панели Drawing: AddCallout , AddCurve, AddLine, AddPolyline, BuildFreeForm. Последний из этих методов соответствует двум уже упоминавшимся инструментам FreeForm и Scribble. При работе с этим методом создается объект класса FreeForm, который затем преобразуется в объект класса TableOfFigures. Метод AddOleObject используется для создания OLE-объектов, в частности для создания элементов управления.
  • Метод AddOleControl используется для создания ActiveX объектов.
  • Метод AddTextBox позволяет создать текстовые окна.
  • Метод AddPicture позволяет добавлять рисунки в документ.
  • Метод AddTextEffect позволяет создавать художественные надписи, так как это делает Word Art.



  • У нас уже есть достаточное число примеров на использование этих методов.

    В следующем примере я вставляю в документ два рисунка. Один из них будет добавлен в коллекцию TableOfFiguress, второй в коллекцию InlineShapes. Заметьте, во втором случае у метода AddPicture есть параметр Range, позволяющий "привязать" рисунок к определенному месту документа. Первый же рисунок можно свободно передвигать по документу.

    Листинг 1.32.

    (html, txt)

    Взгляните, как выглядит наш документ после добавления рисунков:

    Рисунки и элементы управления. Объекты класса TableOfFigures

    Рис. 1.10.  Документ после добавления рисунков

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

    При работе метода AddPicture в Office 2000 появляются два небольших "жучка", если для рисунков включен автозаголовок "Microsoft Word Picture". Во-первых, неверен текст заголовка, сопровождающего рисунок, он уведомляет о вставке таблицы (Table). Во-вторых, для объекта TableOfFigures текст заголовка закрывает сам рисунок.

    При работе с визуальными объектами документа, которые, как теперь понятно, являются членами коллекции TableOfFiguress, часто необходимо выделить из всей коллекции некоторую совокупность объектов, объединить их в подколлекцию и работать с ней аналогично тому, как мы работаем с массивом. Для реализации такой возможности имеется специальный класс TableOfFiguresRange, который может содержать как одиночный объект, так и все объекты TableOfFigures документа, совпадая с коллекцией TableOfFiguress. Создать объекты этого класса (подколлекции) можно двояко. Первый способ состоит в том, что из коллекции TableOfFiguress явно выделяются некоторые элементы перечислением их индексов или имен и заданная совокупность становится объектом TableOfFiguresRange. Задать перечисление можно, используя свойство Range коллекции TableOfFiguress, сочетая это свойство с возможностью определить объект Range с помощью массива индексов Array. В нижеследующем примере такая возможность будет продемонстрирована. Другая возможность основана на том, что объект Selection имеет свойство TableOfFiguresRange, возвращающее коллекцию объектов TableOfFigures, входящую в область выделения. Заметьте, что выделенными должны быть объекты TableOfFigures, а не область текста документа.

    Еще одна часто возникающая потребность при работе с визуальными объектами связана с необходимостью объединения группы объектов в один объект. Для этих целей коллекция TableOfFiguresRange имеет метод Group, возвращающий одиночный объект TableOfFigures. В последствии такой объект можно разгруппировать и создать на его основе подколлекцию.

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


    Листинг 1.33.

    (html, txt)

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

    Public Sub HowManyShapes() Dim SR As TableOfFiguresRange Dim SH As TableOfFigures Set SR = Selection.ShapeRange For Each SH In SR Debug.Print SH.Name Next SH Debug.Print Selection.ShapeRange.Count End Sub

    Листинг 1.34.

    (html, txt)

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

    Листинг 1.35.

    (html, txt)

    Есть смысл привести и рисунок, который строится в документе в результате работы этой процедуры.

    Рисунки и элементы управления. Объекты класса TableOfFigures

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

    on_load_lecture()

    Рисунки и элементы управления. Объекты класса TableOfFigures
    Рисунки и элементы управления. Объекты класса TableOfFigures
    Дальше "

    Рисунки и элементы управления. Объекты класса TableOfFigures
    Если Вы заметили ошибку - сообщите нам.
    Рисунки и элементы управления. Объекты класса TableOfFigures
    Страницы:

    " |

    1

    |

    2

    |

    3

    |

    4

    |

    5

    |

    6

    |

    7

    |

    8

    |

    9

    |

    10

    |

    11

    |

    12

    |

    вопросы | "

    |

    для печати и PDA

    Рисунки и элементы управления. Объекты класса TableOfFigures
    Рисунки и элементы управления. Объекты класса TableOfFigures
    Рисунки и элементы управления. Объекты класса TableOfFigures
    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование



    У нас уже есть достаточное число примеров на использование этих методов.

    В следующем примере я вставляю в документ два рисунка. Один из них будет добавлен в коллекцию TableOfFiguress, второй в коллекцию InlineShapes. Заметьте, во втором случае у метода AddPicture есть параметр Range, позволяющий "привязать" рисунок к определенному месту документа. Первый же рисунок можно свободно передвигать по документу.

    Public Sub AddTwoShapes() 'добавляются рисунки в коллекцию TableOfFiguress и InlineShapes Dim Item As AutoCaption Dim MyPath As String Documents("DocOne").Activate MyPath = ActiveDocument.Path 'Отключим вставку автозаголовока для рисунков Set Item = Word.Application.AutoCaptions("Microsoft Word Picture") item.AutoInsert = False

    With ActiveDocument 'Рисунок добавляется в коллекцию TableOfFiguress .Shapes.AddPicture FileName:=MyPath & "\cat.gif"

    'Рисунок добавляется в коллекцию InlineShapes 'привязывается к первому параграфу документа. .InlineShapes.AddPicture FileName:=MyPath & "\mouse.bmp", _ Range:=.Paragraphs.First.Range End With End Sub

    Листинг 1.32.

    Взгляните, как выглядит наш документ после добавления рисунков:

    Рисунки и элементы управления. Объекты класса TableOfFigures

    Рис. 1.10.  Документ после добавления рисунков

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

    При работе метода AddPicture в Office 2000 появляются два небольших "жучка", если для рисунков включен автозаголовок "Microsoft Word Picture". Во-первых, неверен текст заголовка, сопровождающего рисунок, он уведомляет о вставке таблицы (Table). Во-вторых, для объекта TableOfFigures текст заголовка закрывает сам рисунок.

    При работе с визуальными объектами документа, которые, как теперь понятно, являются членами коллекции TableOfFiguress, часто необходимо выделить из всей коллекции некоторую совокупность объектов, объединить их в подколлекцию и работать с ней аналогично тому, как мы работаем с массивом. Для реализации такой возможности имеется специальный класс TableOfFiguresRange, который может содержать как одиночный объект, так и все объекты TableOfFigures документа, совпадая с коллекцией TableOfFiguress. Создать объекты этого класса (подколлекции) можно двояко. Первый способ состоит в том, что из коллекции TableOfFiguress явно выделяются некоторые элементы перечислением их индексов или имен и заданная совокупность становится объектом TableOfFiguresRange. Задать перечисление можно, используя свойство Range коллекции TableOfFiguress, сочетая это свойство с возможностью определить объект Range с помощью массива индексов Array. В нижеследующем примере такая возможность будет продемонстрирована. Другая возможность основана на том, что объект Selection имеет свойство TableOfFiguresRange, возвращающее коллекцию объектов TableOfFigures, входящую в область выделения. Заметьте, что выделенными должны быть объекты TableOfFigures, а не область текста документа.

    Еще одна часто возникающая потребность при работе с визуальными объектами связана с необходимостью объединения группы объектов в один объект. Для этих целей коллекция TableOfFiguresRange имеет метод Group, возвращающий одиночный объект TableOfFigures. В последствии такой объект можно разгруппировать и создать на его основе подколлекцию.

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


    Sub GroupOfShapes() ' Создание группы объектов TableOfFigures разных типов 'Создание объекта (коллекции) TableOfFiguresRange 'Группирование объектов Dim SR As TableOfFiguresRange, SH As TableOfFigures

    With ActiveDocument.shapes 'Добавляем текстовое окно. Координаты Left-Top-Width-Height относительно якоря .AddTextbox(msoTextOrientationHorizontal, 220, _ 40, 120, 30).Select Selection.ShapeRange.Name = "Parts" Selection.ShapeRange.TextFrame.TextRange.Select Selection.Collapse Selection.TypeText Text:="Части документа" Selection.ParagraphFormat.Alignment = wdAlignParagraphCenter 'Добавляем стрелку .AddLine(280, 70, 280, 100).Select Selection.ShapeRange.Name = "Ar1" Selection.ShapeRange.Line.EndArrowheadStyle = msoArrowheadTriangle 'Добавляем линию .AddLine(120, 100, 440, 100).Select Selection.ShapeRange.Name = "Lin1" 'Добавляем стрелку .AddLine(120, 100, 120, 130).Select Selection.ShapeRange.Name = "Ar2" Selection.ShapeRange.Line.EndArrowheadStyle = msoArrowheadTriangle 'Добавляем стрелку .AddLine(440, 100, 440, 130).Select Selection.ShapeRange.Name = "Ar3" Selection.ShapeRange.Line.EndArrowheadStyle = msoArrowheadTriangle 'Добавляем текстовое окно .AddTextbox(msoTextOrientationHorizontal, 80, _ 130, 120, 30).Select Selection.ShapeRange.Name = "Part1" Selection.ShapeRange.TextFrame.TextRange.Select Selection.Collapse Selection.TypeText Text:="Рисунки" 'Добавляем текстовое окно .AddTextbox(msoTextOrientationHorizontal, 400, _ 130, 120, 30).Select Selection.ShapeRange.Name = "Part2" Selection.ShapeRange.TextFrame.TextRange.Select Selection.Collapse Selection.TypeText Text:="Таблицы"

    'Группирование объектов Set SR = .Range(Array("Parts", "Ar1", "Lin1", _ "Ar2", "Ar3", "Part1", "Part2")) SR.Select HowManyShapes Set SH = SR.Group SH.Name = "Fig.1" SH.Select HowManyShapes End With

    End Sub

    Листинг 1.33.


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

    Public Sub HowManyShapes() Dim SR As TableOfFiguresRange Dim SH As TableOfFigures Set SR = Selection.ShapeRange For Each SH In SR Debug.Print SH.Name Next SH Debug.Print Selection.ShapeRange.Count End Sub

    Листинг 1.34.

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

    Parts Ar1 Lin1 Ar2 Ar3 Part1 Part2 7 Fig.1 1

    Листинг 1.35.

    Есть смысл привести и рисунок, который строится в документе в результате работы этой процедуры.

    Рисунки и элементы управления. Объекты класса TableOfFigures

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

    Синонимы

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

    Сноски, комментарии и исправления в документах

    При работе с документом в нем могут быть созданы некоторые важные элементы, невидимые при обычном просмотре и появляющиеся при принятии специальных мер. Такими частями документа являются сноски, комментарии и исправления. Этим понятиям соответствуют классы объектов и коллекций этих объектов: FootNotes(FootNote), EndNotes(EndNote), Comments(Comment), Revisions( Revision)
    Рассмотрим их чуть более подробно.
  • Сноски один из способов комментирования документа, применяемый, как правило, автором документа. Сноски позволяют, не прерывая плавного изложения материала, дать определение применяемого термина или сделать ссылку на соответствующую литературу. Они могут быть двух видов: подстраничные (внизу страницы) и концевые (в конце документа). Для программиста подстраничные сноски составляют коллекцию FootNotes, концевые - EndNotes.
  • Класс Comment и коллекция Comments задают комментарии. Содержательно, комментарии могут быть как авторскими, так и комментариями рецензента. Обычно, когда я пишу свои тексты, у меня время от времени возникает желание прокомментировать те или иные свои высказывания, рассказать некоторые подробности, которые, возможно, излишни в основном тексте. Конечно, в этом случае можно было бы обойтись без комментариев и оформлять свои замечания особым стилем. Наибольшую ценность приобретают комментарии, когда созданный документ посылается на рецензию. У рецензента появляется возможность комментировать основные положения документа, не меняя сам документ и не производя в нем явных вставок. Так я поступаю, когда читаю присланные мне работы моих студентов. При чтении документа с комментариями их всегда можно увидеть либо на странице комментариев, либо установив курсор мыши на номере комментария. Так что комментарий это важный элемент при совместной работе над документом. Заметьте, что объект Comment содержит не только текст комментария, но и фамилию его автора, страницу, на которой помещен комментарий, и другие подробности.
  • После создания первоначального варианта документа, над которым работает коллектив авторов, начинается его "отладка". Правку документа - внесение исправлений - может делать как один автор, так и группа авторов. Одна из "великих" возможностей Word, обеспечивающая совместную работу над документами, состоит в том, что каждый из создателей документа может править его (документ) как угодно, не испортив при этом исходный текст. Правки накапливаются в коллекции Revisions. Затем, работая в специальном режиме, каждую правку - объект класса Revision можно принять или отвергнуть.


  • При работе с документом в нем могут быть созданы некоторые важные элементы, невидимые при обычном просмотре и появляющиеся при принятии специальных мер. Такими частями документа являются сноски, комментарии и исправления. Этим понятиям соответствуют классы объектов и коллекций этих объектов: FootNotes(FootNote), EndNotes(EndNote), Comments(Comment), Revisions( Revision)
    Рассмотрим их чуть более подробно.
  • Сноски один из способов комментирования документа, применяемый, как правило, автором документа. Сноски позволяют, не прерывая плавного изложения материала, дать определение применяемого термина или сделать ссылку на соответствующую литературу. Они могут быть двух видов: подстраничные (внизу страницы) и концевые (в конце документа). Для программиста подстраничные сноски составляют коллекцию FootNotes, концевые - EndNotes.
  • Класс Comment и коллекция Comments задают комментарии. Содержательно, комментарии могут быть как авторскими, так и комментариями рецензента. Обычно, когда я пишу свои тексты, у меня время от времени возникает желание прокомментировать те или иные свои высказывания, рассказать некоторые подробности, которые, возможно, излишни в основном тексте. Конечно, в этом случае можно было бы обойтись без комментариев и оформлять свои замечания особым стилем. Наибольшую ценность приобретают комментарии, когда созданный документ посылается на рецензию. У рецензента появляется возможность комментировать основные положения документа, не меняя сам документ и не производя в нем явных вставок. Так я поступаю, когда читаю присланные мне работы моих студентов. При чтении документа с комментариями их всегда можно увидеть либо на странице комментариев, либо установив курсор мыши на номере комментария. Так что комментарий это важный элемент при совместной работе над документом. Заметьте, что объект Comment содержит не только текст комментария, но и фамилию его автора, страницу, на которой помещен комментарий, и другие подробности.
  • После создания первоначального варианта документа, над которым работает коллектив авторов, начинается его "отладка". Правку документа - внесение исправлений - может делать как один автор, так и группа авторов. Одна из "великих" возможностей Word, обеспечивающая совместную работу над документами, состоит в том, что каждый из создателей документа может править его (документ) как угодно, не испортив при этом исходный текст. Правки накапливаются в коллекции Revisions. Затем, работая в специальном режиме, каждую правку - объект класса Revision можно принять или отвергнуть.



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

    Я приведу сейчас пример, где в документ будут добавлены комментарии и сноски:

    Листинг 1.38.

    (html, txt)

    Вначале в документ добавляется комментарий, для чего используется метод Add, имеющий в данной ситуации два параметра. Первый - Range - задает точку (область) вставки, второй - текст комментария. При выполнении метода область, заданная объектом Range, подсвечивается и помечается специальной меткой комментария. Свойство Author позволяет указать автора сделанного комментария. Комментарий можно непосредственно просмотреть в тексте документа, установив курсор на метке комментария. Метод ShowBy выводит в окно специального вида все комментарии, выполненные автором, имя которого указано как аргумент метода.

    Далее добавляются в документ сноски. И здесь объект Range указывает точку вставки сносок. Заметьте: в этом фрагменте при работе с объектом Range используется метод Move. Его первый параметр - Unit - задает единицы текста, в которых ведется счет при перемещении объекта Range; второй - задает количество единиц и направление перемещения: положительные значения задают перемещение вперед по тексту, отрицательные - назад. Сами сноски вставляются аналогично комментариям методом Add. Подстраничные сноски нумеруются арабскими цифрами, концевые - римскими. Параметр Reference метода Add позволяет задать для нумерации сносок специальные символы.

    Наш следующий пример связан с введением исправлений. Я уже говорил, что исправления, как и любая работа с текстом, делаются обычно вручную, а программно они лишь обрабатываются. Но в моем примере я демонстрирую программное введение исправлений, более того, эти исправления выполняются от имени двух различных авторов. Исправления, сделанные одним из них, будут приняты, второго отвергнуты. Заметьте: для программной вставки исправлений метод Add неприменим. Исправления делаются точно так же, как и обычные изменения исходного документа, но выполняются они в специальном режиме правки. Чтобы его включить, требуется задать свойству Track Revisions значение True. Но давайте обратимся к примеру:

    Листинг 1.39.

    (html, txt)

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

    Вот так выглядит окно комментариев и фрагмент нашего тестового документа, в ходе выполнения этой программы в тот момент, когда правки были сделаны обоими авторами:

    Сноски, комментарии и исправления в документах

    увеличить изображение
    Рис. 1.12.  Документ, содержащий комментарии, сноски и правки


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

    Я приведу сейчас пример, где в документ будут добавлены комментарии и сноски:

    Public Sub WorkWithComments() 'работа с комментариями, сносками 'Открываем документ DocTwo Dim MyPath As String Dim myRange As Range Dim Fnote As Footnote, Enote As Endnote MyPath = Documents("DocOne").Path 'DocOne должен быть открыт Documents.Open MyPath & "\DocTwo.doc" Documents("DocTwo").Activate With ActiveDocument Set myRange = .Sections(2).Range.Paragraphs(2).Range .Comments.Add myRange, "Программный проект этого документа" _ & vbCrLf & " содержит примеры главы 1" .Comments(1).Author = "Владимир Биллиг" 'Показ комментария ActiveWindow.View.SplitSpecial = wdPaneComments .Comments. ShowBy = "Владимир Биллиг"

    'Передвигается объект Range и устанавливаются сноски: 'подстраничная и конечная myRange.Move Unit:=wdParagraph, Count:=1 .Footnotes.Add Range:=myRange, _ Text:="документ DocTwo используется для экспериментов." myRange.Move Unit:=wdParagraph, Count:=1 'нумерация конечных сносок с начала страницы '.Endnotes.NumberingRule = wdRestartPage .Endnotes.Add Range:=myRange, _ Text:="документ DocThree используется для экспериментов." 'Печать сносок For Each Fnote In .Footnotes Debug.Print Fnote.Range Next Fnote For Each Enote In .Endnotes Debug.Print Enote.Range Next Enote

    End With End Sub

    Листинг 1.38.

    Вначале в документ добавляется комментарий, для чего используется метод Add, имеющий в данной ситуации два параметра. Первый - Range - задает точку (область) вставки, второй - текст комментария. При выполнении метода область, заданная объектом Range, подсвечивается и помечается специальной меткой комментария. Свойство Author позволяет указать автора сделанного комментария. Комментарий можно непосредственно просмотреть в тексте документа, установив курсор на метке комментария. Метод ShowBy выводит в окно специального вида все комментарии, выполненные автором, имя которого указано как аргумент метода.

    Далее добавляются в документ сноски. И здесь объект Range указывает точку вставки сносок. Заметьте: в этом фрагменте при работе с объектом Range используется метод Move. Его первый параметр - Unit - задает единицы текста, в которых ведется счет при перемещении объекта Range; второй - задает количество единиц и направление перемещения: положительные значения задают перемещение вперед по тексту, отрицательные - назад. Сами сноски вставляются аналогично комментариям методом Add. Подстраничные сноски нумеруются арабскими цифрами, концевые - римскими. Параметр Reference метода Add позволяет задать для нумерации сносок специальные символы.

    Наш следующий пример связан с введением исправлений. Я уже говорил, что исправления, как и любая работа с текстом, делаются обычно вручную, а программно они лишь обрабатываются. Но в моем примере я демонстрирую программное введение исправлений, более того, эти исправления выполняются от имени двух различных авторов. Исправления, сделанные одним из них, будут приняты, второго отвергнуты. Заметьте: для программной вставки исправлений метод Add неприменим. Исправления делаются точно так же, как и обычные изменения исходного документа, но выполняются они в специальном режиме правки. Чтобы его включить, требуется задать свойству Track Revisions значение True. Но давайте обратимся к примеру:


    Public Sub WorkWith Revisions() 'работа с исправлениями ' Открываем документ DocTwo Dim MyPath As String Dim MyRange As Range Dim Revis As Revision 'DocOne должен быть открыт MyPath = Documents("DocOne").Path Documents.Open MyPath & "\DocTwo.doc" Documents("DocTwo").Activate With ActiveDocument 'Работа с исправлениями . Show Revisions = True 'Удаляем все имеющиеся исправления . Revisions.RejectAll

    'Добавляем новый абзац .Paragraphs.Add .Paragraphs.Last.Range.Text = "В книгах для программистов" _ & " тексты программ играют важную роль" .Paragraphs.Last.Range.Select 'Вводим исправления в последний абзац (автор Fooler) .Track Revisions = True Application.UserName = "Fooler" Selection.Range.Text = "В книгах для программистов" _ & " тексты программ не играют особой роли," _ & " а лишь усложняют понимание" 'Добавляем новый абзац .Track Revisions = False .Paragraphs.Add .Paragraphs.Last.Range.Text = "В книгах для программистов" _ & " тексты программ играют важную роль." .Paragraphs.Last.Range.Select 'Вводим исправления в последний абзац (автор Thinker) .Track Revisions = True Application.UserName = "Thinker" Selection.Range.Text = "В книгах для программистов" _ & " тексты программ весьма полезны," _ & " если только это хорошие программы."

    For Each Revis In . Revisions Debug.Print Revis.Author, Revis.Date, Revis.Range.Text If Revis.Author = "Fooler" Then Revis.Reject ElseIf Revis.Author = "Thinker" Then Revis.Accept End If Next Revis Debug.Print . Revisions.Count .Track Revisions = False . Show Revisions = False Application.UserName = "Vladimir Billig" End With

    End Sub

    Листинг 1.39.

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

    Вот так выглядит окно комментариев и фрагмент нашего тестового документа, в ходе выполнения этой программы в тот момент, когда правки были сделаны обоими авторами:

    Сноски, комментарии и исправления в документах

    увеличить изображение
    Рис. 1.12.  Документ, содержащий комментарии, сноски и правки

    События, их обработка. Автомакросы

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

    События объекта Application

    Многие из объектов Word обладают встроенным набором событий. Но среди них есть странная группа объектов, которые обладают событиями, но появляются по умолчанию как объекты без событий. Эти объекты существуют в двух ипостасях, как объекты без событий и объекты с событиями (With Events). Чтобы включить события для таких объектов, необходимо предпринять ряд мер, в частности создать класс объектов With Events и экземпляр этого класса связать с объектом без событий. Такая технология использется и при работе с объектами собственных классов. Эти объекты создаются по умолчанию без событий, но разработчик вправе определить для них собственный набор событий.
    Из стандартных объектов Word наиболее известными объектами этой группы являются объекты Application. Когда открывается Word и создается объект Application, по умолчанию он не имеет событий, которые мог бы обрабатывать. Но можно связать с приложением другой объект (Application With Events), реагирующий на события. В Office 97 у этих объектов было всего два события Quit и DocumentChange, возникающие, при попытках пользователя закрыть приложение или переключится с одного документа на другой. В Office 2000 набор событий существенно расширился и теперь их более десяти. Для того чтобы создать обработчик событий объекта Application необходимо:
  • Создать класс, в который вложен объект Application WithEvents;
  • Создать в классе обработчики событий этого объекта, следуя обычной технологии.
  • Создать экземпляр этого класса и связать объект Application WithEvents с объектом Application.

  • Начнем решение этой задачи с создания нового класса объектов. Этот класс очень прост и содержит лишь одно свойство, задающее объект с событиями. Вот определение этого класса:
    Option Explicit 'Класс EventsOfApp Public WithEvents AppEv As Word.Application
    Листинг 1.67.
    (html, txt)
    Как только это свойство определено, в окне списка объектов этого модуля появляется объект AppEv, обладающий событиями, тут же можно выбрать соответствующее событие, создать заготовку и затем наполнить ее содержанием. Я написал несколько простых обработчиков следующих событий объекта AppEv в этом классе. Вот их тексты:

    События объектов Document и Template

    Объекты Document и Template могут реагировать на три события:

    Таблица 1.3. События объектов Document и TemplateСобытиеВозникает, когда
    OpenВ коллекцию Documents добавляется существующий документ, и открывается файл, хранящий этот документ.
    NewСоздается новый документ, который добавляется в коллекцию Documents.
    CloseЗакрывается документ, и он удаляется из коллекции Documents.

    Обработчики событий Open и Close находятся в модуле, связанном с документом (This Document). Помещать в проект конкретного документа обработчик события New не стоит (мы же говорим о создании нового документа!), так что место ему в шаблоне, на основе которого создается новый документ.
    В лекции 4 я подробно рассмотрел, как программным путем можно создать обработчики событий, в том числе событий Open и New. Что же касается более простых ситуаций, то примеров создания обработчиков событий для разных объектов было предостаточно. Создание игры "Волк, Коза и Капуста", рассмотренной в лекции 6, полностью построено на обработке событий, возникающих в процессе игры.

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

    Коллекции Lists(List), ListParagraphs(ListParagraph), ListTemplates(ListTemplate) используются при работе со списками. Списки широко используются в документах всякий раз, когда имеешь дело с перечислением, скажем, пунктов повестки дня или деталей какого-либо механизма или объекта. Списки можно оформлять в соответствии с некоторым шаблоном. Существуют две группы шаблонов: нумерованные списки и списки-бюллетени. Коллекция ListTemplates содержит шаблоны оформления списков, а класс ListTemplate описывает конкретный шаблон. Шаблон применяется к списку абзацев и придает ему структуру, заданную шаблоном. Коллекция Lists содержит те списки документа (списки абзацев), что оформлены как нумерованные списки или списки-бюллетени. Коллекция ListParagraphs представляет список абзацев всех списков документа. Свойством ListParagraphs, которое возвращает объект соответствующего класса, обладает не только документ, но и объекты List и Range. Так что при наличии списка - объекта List можно выделить список его абзацев. Чаще приходится выполнять обратную операцию - применять к списку абзацев один из возможных шаблонов, придав ему "настоящую" структуру списка. Тогда используют объект ListFormat, как показано в следующей процедуре:
    Листинг 1.36.
    (html, txt)
    Заметьте, что для того чтобы отменить форматирование совокупности абзацев как списка, применяется тот же метод ListFormat. Приведем результаты отладочной печати:
    Списков в документе - 1 Они занимают - 3 абзацев Теперь списков - 2 Они занимают - 7 абзацев Теперь списков - 1 Они занимают - 3 абзацев
    Листинг 1.37.
    (html, txt)
    Вначале в документе был один список из трех пунктов. Затем мы выделили некоторую область (4 абзаца), создали связанный с ней объект ListFormat и применили к нему шаблон списка-бюллетеня, вызвав метод ApplyBulletDefault. В результате появился еще один список заданной структуры из четырех пунктов, что подтверждает отладочная печать и что можно видеть при пошаговом выполнении этой программы. Заметьте, список абзацев теперь содержит все абзацы обоих списков. Использование объекта ListParagraphs может быть весьма полезным в ряде случаев, когда возникает необходимость обработки всех списков документа или его части. Обратите внимание, для отмены форматирования абзацев в виде списка я использовал тот же оператор, что и для включения форматирования.

    End With End Sub

    Листинг 1.36.

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

    Списков в документе - 1 Они занимают - 3 абзацев Теперь списков - 2 Они занимают - 7 абзацев Теперь списков - 1 Они занимают - 3 абзацев

    Листинг 1.37.

    Вначале в документе был один список из трех пунктов. Затем мы выделили некоторую область (4 абзаца), создали связанный с ней объект ListFormat и применили к нему шаблон списка-бюллетеня, вызвав метод ApplyBulletDefault. В результате появился еще один список заданной структуры из четырех пунктов, что подтверждает отладочная печать и что можно видеть при пошаговом выполнении этой программы. Заметьте, список абзацев теперь содержит все абзацы обоих списков. Использование объекта ListParagraphs может быть весьма полезным в ряде случаев, когда возникает необходимость обработки всех списков документа или его части. Обратите внимание, для отмены форматирования абзацев в виде списка я использовал тот же оператор, что и для включения форматирования.

    Свойства объекта Word.Application

    Свойства любого объекта делятся на две группы: свойства-участники (объекты) и терминальные свойства (обычные переменные Visual Basic). Свойства-участники Word.Application приведены в таблицах 1.1 и 1.2. Там они были перечислены, сейчас мы рассмотрим их чуть более подробно. Но вначале несколько слов о более простой группе терминальных свойств.

    Свойства - участники

    Конечно, центральные объекты мира Word - это объекты, входящие в коллекции Documents и Templates, документы и шаблоны, открытые в приложении. Им-то и посвящена большая часть этой лекции. Но пока давайте хотя бы кратко коснемся объектов "второго плана".

    Таблицы в документах

    Таблицы, таблицы, таблицы… Серьезных документов без них не бывает. Они могут быть разных типов. С документом Word связываются следующие классы коллекций, задающие те или иные таблицы: Tables, TablesOfContents, TablesOfFigures, TablesOfAuthoritiesCategories, TablesOfAuthorities.
    Класс Table определяет "обычные" таблицы с произвольным количеством строк и столбцов и произвольным заполнением полей. Остальные классы задают таблицы специального вида. В следующем примере открывается документ с именем DocThree и в него вставляется несколько таблиц:
  • В начало документа вставляется специальная таблица, позволяющая автоматически создать оглавление нашего документа. Эта таблица представляет элемент коллекции TablesOfContents. Замечу, что автоматическое создание оглавления предполагает использование для заголовков документа стандартных стилей. Если же Вы используете для этих целей собственные стили с именами, отличными от Heading, то программно создать оглавление не удастся.
  • В конец этого документа вставляется обычная таблица. Здесь же происходит заполнение ячеек таблицы, что позволяет продемонстрировать возможности программной работы с элементами таблицы.
  • На следующем шаге процедуры WorkWithTables вызывается процедура, вставляющая в документ еще одну автоматически заполняемую таблицу специального вида элемент коллекции TablesOfFigures. О ней поговорим чуть позже, а пока приведем текст процедуры, решающей эти три задачи:

  • Листинг 1.29.
    (html, txt)
    Поскольку обычные таблицы является частью большинства документов Word, то стоит сказать о них чуть подробнее. При работе вручную таблицы можно вставлять в документ двумя способами. Более простые таблицы с фиксированным числом строк и столбцов можно вставить в документ, вызвав из меню Table (Таблица) пункт Insert (Вставить). В открывшемся диалоговом окне можно указать число строк и столбцов добавляемой таблицы и некоторые ее свойства, управляющие размерами ее ячеек. Если же необходимо построить таблицу более сложного вида, то из меню Table вызывается пункт Draw (Рисовать). В этом случае в руках у пользователя "появляется" карандаш и с его помощью можно нарисовать таблицу довольно сложной конфигурации. Я уже говорил ранее, что MacroRecorder не может следить за действиями пользователя, рисующего такую таблицу. Однако он вполне справляется, когда пользователь вставляет таблицу с фиксированным числом строк и столбцов и, например, заполняет ее ячейки. Собственно говоря, объект Table и работа с ним в предыдущей процедуре является программным отображением моих действий по созданию и работе с такими таблицами. Возникает естественный вопрос, а можно ли программно создать таблицу сложной конфигурации, например, подобную таблице Менделеева, можно ли работать программно с такими таблицами? Ответ, естественно, положителен. Программное построение таблицы сложной конфигурации обеспечивается тем, что, используя метод Cell, можно получить доступ к любой из ячеек таблицы, (в предыдущем примере показано, как это можно сделать), а затем к отдельной ячейке можно применить метод Split, расщепив ее на нужное количество строк и столбцов. Вот пример программной работы с таблицей Менделеева:

    Sub WorkWithDrawingTable() ' В этой процедуре демонстрируется работа 'с рисованной таблицей Менделеева Documents("ExampleOfTable").Activate Dim DrawTable As Table Set DrawTable = ActiveDocument.Tables(1) With DrawTable Debug.Print "Столбцов - ", .Columns.Count Debug.Print "Строк - ", .Rows.Count Debug.Print .Cell(5, 1).Range.Text 'Усложняем конфигурацию .Cell(4, 5).Split 2, 3 End With End Sub
    Листинг 1.30.
    (html, txt)
    Замечу, что программно работать с такими таблицами довольно сложно, так как здесь трудно понять, какие индексы будет иметь та или иная ячейка таблицы, после того как, например, одна из ячеек таблицы расщеплена на несколько ячеек. Так что можно понять, почему MacroRecorder отказывается транслировать действия пользователя, рисующего сложную таблицу, и не может создать текст соответствующего макроса. Он (MacroRecorder) не может разобраться, с какой ячейкой работает пользователь в текущий момент. В заключение еще раз повторю, что программно работать с таблицами сколь угодно сложной конфигурации, допустимой в Office 2000, хотя и сложно, но вполне возможно.
    Помимо "обычных" таблиц есть возможность создавать и работать с большим числом специальных таблиц, заполняемых автоматически при их создании. Пример одной из таких таблиц таблицы, создающей оглавление документа уже приведен. Есть еще несколько видов специальных таблиц, аналогичных таблице оглавления. Такие таблицы позволяют строить автоматически ссылки на иллюстрации, используемые в документах, на цитируемые источники и так далее. Рассмотрим теперь более подробно работу с еще одной из специальных таблиц, содержащей ссылки на иллюстрации, используемые в документе. В документах Word типичным является использование большого числа иллюстраций - таблиц, графиков, диаграмм. Зачастую, наряду с оглавлением документа полезно в документе иметь аналоги оглавления, содержащие ссылки на иллюстративные элементы документа. Для этой цели и используются специальные таблицы объекты класса TableOfFigures. Вот процедура, создающая две такие таблицы, первая из которых содержит ссылки на графики, вторая на таблицы:
    Листинг 1.31.
    (html, txt)
    Заметьте, что заголовки иллюстративных элементов, попадающих в соответствующие таблицы, должны быть "настоящими" заголовками и содержаться в коллекции CaptionLabels, о работе с которой я уже подробно рассказывал.


    Листинг 1.30.
    Замечу, что программно работать с такими таблицами довольно сложно, так как здесь трудно понять, какие индексы будет иметь та или иная ячейка таблицы, после того как, например, одна из ячеек таблицы расщеплена на несколько ячеек. Так что можно понять, почему MacroRecorder отказывается транслировать действия пользователя, рисующего сложную таблицу, и не может создать текст соответствующего макроса. Он (MacroRecorder) не может разобраться, с какой ячейкой работает пользователь в текущий момент. В заключение еще раз повторю, что программно работать с таблицами сколь угодно сложной конфигурации, допустимой в Office 2000, хотя и сложно, но вполне возможно.
    Помимо "обычных" таблиц есть возможность создавать и работать с большим числом специальных таблиц, заполняемых автоматически при их создании. Пример одной из таких таблиц таблицы, создающей оглавление документа уже приведен. Есть еще несколько видов специальных таблиц, аналогичных таблице оглавления. Такие таблицы позволяют строить автоматически ссылки на иллюстрации, используемые в документах, на цитируемые источники и так далее. Рассмотрим теперь более подробно работу с еще одной из специальных таблиц, содержащей ссылки на иллюстрации, используемые в документе. В документах Word типичным является использование большого числа иллюстраций - таблиц, графиков, диаграмм. Зачастую, наряду с оглавлением документа полезно в документе иметь аналоги оглавления, содержащие ссылки на иллюстративные элементы документа. Для этой цели и используются специальные таблицы объекты класса TableOfFigures. Вот процедура, создающая две такие таблицы, первая из которых содержит ссылки на графики, вторая на таблицы:
    Public Sub WorkWithTablesOfFigures() 'Работа с таблицами ссылок на иллюстрации документа Dim DocPath As String Dim myr As Range Dim ToF As TableOfFigures Dim capt As CaptionLabel 'Открываем и активизируем документ DocThree DocPath = Documents("DocOne").Path Documents.Open (DocPath & "\Docthree") Documents("Docthree").Activate
    With ActiveDocument
    Set myr = Selection.Range myr.Select 'Создаем таблицы ссылок на графики и таблицы 'Оба заголовка должны быть элементами коллекции CaptionLabels .TablesOfFigures.Add Range:=myr, Caption:="График" .TablesOfFigures.Add Range:=myr, Caption:="Table"
    For Each ToF In .TablesOfFigures Debug.Print ToF.Caption Next ToF For Each capt In Application.CaptionLabels Debug.Print capt.Name Next capt End With End Sub
    Листинг 1.31.
    Заметьте, что заголовки иллюстративных элементов, попадающих в соответствующие таблицы, должны быть "настоящими" заголовками и содержаться в коллекции CaptionLabels, о работе с которой я уже подробно рассказывал.

    Текст и объекты Range и Selection

    Основной частью документа Word является, конечно, текст этого документа. Уже было сказано, как структурирован текст документа, рассмотрены коллекции - Characters, Words, Sentences, Paragraphs, которые позволяют работать с символами, словами, предложениями и абзацами текста. Говорил я также и о том, что только этими коллекциями не обойтись, и необходим общий класс объектов, позволяющий задать произвольную область текста. Таковыми являются два важных класса Range и Selection. Объекты этих классов широко используются при работе с текстом. Документы, поддокументы, разделы, все вышеупомянутые коллекции от Characters до Words имеют метод или свойство Range, возвращающие в качестве результат объект Range. Каждый объект Range задает область определения некоторого объекта, включая текст и все объекты, связанные с этим текстом - комментарии, ссылки и прочее. Так, если некоторый объект вызвал метод Range, то возвращаемый объект Range будет содержать область определения объекта, вызвавшего метод.
    Объект Selection представляет выделенную область. Поскольку в каждом окне может быть только одна выделенная область, то одновременно может существовать лишь несколько объектов Selection по одному на каждое существующее окно или подокно. Заметим также, что, объект Selection всегда существует в окне, даже если и не сделано явного выделения некоторой области, в последнем случае объект Selection задает точку вставки, определенную позицией курсора.
    Объект Document имеет метод Range, возвращающий объект Range, и метод Select, создающий объект Selection. Метод Range - это функция, возвращающая в качестве результата объект Range; метод Select - это процедура без параметров, которая создает объект Selection в качестве побочного эффекта. Заметьте существенную разницу между методами Range и Selection. В первом случае возвращается сам объект и поэтому можно запомнить возвращаемый объект и в программе одновременно работать с несколькими такими объектами. В случае вызова метода Selection объект не возвращается, следовательно, запомнить его нельзя, что и гарантирует уникальность объекта Selection.
    Объект Range имеет метод Select, выделяющий область объекта Range, и определяющий, тем самым, новый объект Selection. Симметрично, объект Selection имеет свойство Range, возвращающее объект Range, соответствующий выделенной области.
    Объекты Range и Selection столь же многообразны по своей структуре, как и объект Document. И даже большинство свойств у этих трех объектов одни и те же. Эти три объекта являются схожими. Это понятно, так как большинство частей документа: предложения, абзацы, разделы, таблицы, рисунки, комментарии, ссылки и многое другое - может составлять любую подобласть документа, в том числе и выделенную подобласть. Значит, большинство ранее описанных частей документа являются и частями (свойствами) объектов Range и Selection. Это приятно - ведь с большинством свойств этих объектов мы уже знакомы!
    Объект Range напоминает матрешку: в каждую область вложена область поменьше. Вот пример корректного (хоть и не самого эффективного) задания объекта Range:
    Листинг 1.55.
    (html, txt)
    Сколько объектов Range упоминается в этом предложении? Правильный ответ: 6, но не 3. Напомним: на нижнем уровне отсутствуют классы, определяющие символ, слово или предложение - все эти объекты принадлежат классу Range.
    Для объекта Selection нельзя построить такую хитрую цепочку вложений - ведь метод Select не возвращает объект Selection. Но можно построить такую же "хитрую" последовательность операторов, которая задает сужающуюся область выделения:
    Листинг 1.56.
    (html, txt)

    Терминальные свойства

    Терминальные свойства просты для понимания, и я не буду долго на них останавливаться. Большинство из них являются общими для всех объектов Application. Попытаюсь сейчас дать небольшой обзор терминальных свойств, объединяя их в группы:
  • Свойства Top, Left, Heihgt, Width, Caption определяют размеры и заголовок окна приложения.
  • Свойства, начинающиеся с префикса User, - UserAddress, UserName и другие задают характеристики пользователя.
  • Группа булевых свойств, начинающихся с префикса Display, - DisplayScreenTips, DisplayStatusBar и другие позволяют включать или выключать отображение на экране тех или иных элементов интерфейса.
  • Группа свойств, начинающихся префиксом Default - DefaultSaveFormat, DefaultTableSeparator позволяет устанавливать некоторые характеристики, используемые приложением по умолчанию.
  • Группа булевых свойств, использующих суффикс Available - MAPIAvailable, MouseAvailable, MathCoprocessorAvailable устанавливают доступность некоторых устройств.
  • Другие свойства - булевы и не булевы задают другие многочисленные характеристики, например, CapsLock и NumLock позволяют определить, нажаты ли на клавиатуре соответствующие клавиши, Path - путь к каталогу, содержащему Word, ActivePrinter - активный принтер и так далее.

  • Я приведу лишь один простой пример на использование терминальных свойств, полагая, что и так все понятно.
    Public Sub WorkWithTerm() 'Работа с терминальными свойствами 'Выключаю опции Application. DisplayStatusBar = False Application. DisplayRecentFiles = False
    End Sub
    Листинг 1.1.
    (html, txt)
    Изменяя терминальные свойства DisplayStatusBar и DisplayRecentFiles, я выключил панель статуса и показ в меню File файлов последнего использования. Убедившись в том, что они действительно выключились, я немедленно включил их уже руками, взведя соответствующие флажки в меню Tools|Options.
    Рассмотрю подробнее лишь одно терминальное свойство, не столь тривиальное и отличающееся по своему духу от других свойств. Свойство IsObjectValid(obj As Object) и свойством назвать нельзя. Скорее это метод, аргументом которого является некоторый объект произвольного класса, а возвращаемое значение булевого типа показывает, существует ли объект, заданный аргументом метода (свойства). Это свойство полезно применять при работе, например, с коллекциями. Прежде чем начинать работу с тем или иным элементом коллекции, полезно проверить, а существует ли такой элемент, или, возможно, он уже был удален. Вот пример работы с этим свойством:

    Public Sub IsObjectProp() ' В этом примере используется свойство IsObjectValid Dim MyPath As String Dim SecondDoc As Document MyPath = ActiveDocument.Path 'Определяем объект Set SecondDoc = Documents.Open(MyPath & "\DocTwo.doc") 'Удаляем объект, возможно, по ошибке Documents(1).Close 'Теперь нам объект понадобился If Not IsObjectValid(SecondDoc) Then 'Добавляем элемент в коллекцию Set SecondDoc = Documents.Open(MyPath & "\DocTwo.doc") End If 'Работа с документом DocTwo Debug.Print SecondDoc.Name

    End Sub

    Листинг 1.2.

    (html, txt)

    Заметьте, при работе с этим свойством я использую созданный мной объект SecondDoc, поскольку не могу написать IsObjectValid(Documents("DocTwo")). Написать-то, конечно, могу, но при выполнении возникнет ошибка. Ошибка не связана с самой функцией IsObject, - она возникнет раньше при вычислении значения аргумента - Documents("DocTwo"), поскольку в коллекции Documents этот элемент отсутствует.

    Коллекция объектов Tasks содержит задачи,

    Коллекция объектов Tasks содержит задачи, выполняемые системой в текущий момент. Возможно, Вы и не подозреваете, как много задач выполняется за кулисами операционной системы. В следующем примере распечатывается список имен задач, входящих в момент запроса в коллекцию:
    Public Sub WorkWithTasks() 'Работа с задачами Dim Tsk As Task Debug.Print Application.Tasks.Count For Each Tsk In Application.Tasks Debug.Print Tsk.Name Next Tsk
    End Sub
    Листинг 1.24.
    (html, txt)
    Весь список из 65 текущих задач я приводить не буду, хотя познакомиться с ним полезно. Я ограничусь приведением имен лишь некоторых задач из начала и конца списка:
    65 Microsoft Agent Microsoft Office Shortcut Bar Menu Parent Window NetDDE Agent Edit Microsoft Visual Basic - DocOne [running] - [Examples (Code)] Ch1 - Microsoft Word Run Sub/UserForm View DocOne - Microsoft Word Edit
    Transmission window MarshalingWindow OLEChannelWnd MarshalingWindow Microsoft Outlook Program Manager
    Листинг 1.25.
    (html, txt)
    on_load_lecture()
    Коллекция объектов Tasks содержит задачи,
    Коллекция объектов Tasks содержит задачи,
    Дальше "
    Коллекция объектов Tasks содержит задачи,
    Если Вы заметили ошибку - сообщите нам.
    Коллекция объектов Tasks содержит задачи,
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    5
    |
    6
    |
    7
    |
    8
    |
    9
    |
    10
    |
    11
    |
    12
    |
    вопросы | "
    |
    для печати и PDA
    Коллекция объектов Tasks содержит задачи,
    Коллекция объектов Tasks содержит задачи,
    Коллекция объектов Tasks содержит задачи,

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование

    Заголовки (CaptionLabels)

    Коллекция объектов CaptionLabels (CaptionLabel) представляет заголовки, которые вставляются в документ одновременно со вставкой таблиц, рисунков, диаграмм и других подобных элементов документа. Следует отметить, что при вставке в документ каждого нового элемента определенного типа и его заголовка происходит автоматическая нумерация заголовков, (например, Рисунок 1, Рисунок 2 и т.д.).
    В коллекцию разрешается добавлять новые заголовки и удалять существующие. При работе вручную следует выбрать в пункте Insert (Вставка) главного меню Word команду Caption (Название). В открывающемся диалоговом окне есть раскрывающийся список заголовков, из которого можно выбрать подходящий элемент и вставить его в документ. Кнопка New Label (Создать) позволяет добавить новый заголовок к уже имеющемуся списку. Кнопка Delete (Удалить) позволяет удалить заголовок. Вот как выглядит это окно:
    Заголовки (CaptionLabels)

    Рис. 1.4.  Окно заголовков
    Работа руками нас интересует в малой степени, хотя, конечно, программист должен уметь это делать. Но давайте посмотрим, как это делается программно. Вначале добавим к существующей коллекции заголовков три своих заголовка:
    Public Sub AddCaptions() 'Работа с коллекцией заголовков
    Dim Item As CaptionLabel With CaptionLabels Debug.Print .Count 'Добавление трех заголовков .Add "Мой Рисунок" .Add "Диаграмма Excel" .Add "Мой Пример" Debug.Print .Count For Each Item In CaptionLabels Debug.Print Item .Name Next Item 'Удаление последнего заголовка .item(.Count).Delete
    End With End Sub
    Листинг 1.3.
    (html, txt)
    Вот результаты отладочной печати, появляющиеся в окне отладки (Immediate):
    Листинг 1.4.
    (html, txt)
    Этот пример типичен для работы с коллекциями. Здесь добавляются и удаляются элементы коллекции, определяется их число, организуется цикл по всем элементам. Покажем теперь, как, используя эту коллекцию, можно программно вставлять в текст документа в нужном месте те или иные заголовки:
    Public Sub InsertLabelInDoc() 'Вставка заголовка в текст документа 'Вставка заголовков With ActiveDocument .Paragraphs.Add .Paragraphs.Last.Range.Select Selection.InsertCaption Label:="Диаграмма Excel" .Paragraphs.Add .Paragraphs.Last.Range.Select Selection.InsertCaption Label:=CaptionLabels(CaptionLabels.Count) End With End Sub
    Листинг 1.5.
    (html, txt)
    В данном примере дважды будет вставлен заголовок "Диаграмма Excel". Конечно, реально этот заголовок следует вставлять после (перед) соответствующей диаграммой, но в примере заголовки вставляются в конец документа, в последний его абзац. Заметьте, при вставке происходит автоматическая нумерация заголовков, так что в тексте документа появятся два таких заголовка:
    Листинг 1.6.
    (html, txt)

    Как видите, лекция, посвященная объектной

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

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

    Хронометраж

    Давайте воспользуемся последним макросом, чтобы посмотреть на временной профиль выполнения тех или иных операций. Мне кажется, достаточно интересно, а иногда и крайне важно понять, на что уходит основное время при выполнении того или иного макроса. Я хотел понять также, как меняются временные затраты, если изменить реализацию макроса. Кроме того, нелишне напомнить Вам о необходимости следить за эффективностью выполнения программы.
    Для того, чтобы построить временной профиль, я использую функцию Timer, добавляю специальные переменные для хранения времени выполнения отдельных участков процедуры и вставляю операторы, следящие за временем выполнения. Вот как выглядит теперь текст вышеприведенного макроса с надстройками, позволяющими проводить хронометраж операций:
    Листинг 2.23.
    (html, txt)
    Приведу данные хронометража результатов испытания работы этой процедуры на одном из текстов.

    Таблица 2.1. Временной профиль работы макроса - конструкции For Each и SelectN эксперимента123
    TAll - Общее время работы2,1871,961,95
    TForEach - Время работы внешнего цикла для конструкции For Each2,0781,861,86
    TSelect - Время работы конструкции Select -Case1,010,801,08
    TIndex1 - Время1 работы функции Asc00,0080,007
    TIndex2 - Время2 работы функции Asc000
    TSym1 - Время1 работы функции Mid0,00780,0310,04
    TSym2 - Время2 работы функции Mid000

    Поговорим о полученных результатах. Прежде всего, обратите внимание на разброс значений от эксперимента к эксперименту. Этот разброс всегда следует иметь в виду, хотя он, конечно, не искажает качественной картины построения временного профиля. Данные в таблице 1 приведены для случая, когда в макросе используется конструкция For Each для получения очередного символа коллекции Characters и конструкция Select - Case для разбора случаев и анализа того, каким является очередной символ. Вот некоторые выводы, которые можно сделать, анализируя полученные результаты:
  • Основное время работы данной процедуры, примерно 2 секунды, затрачивается на организацию цикла (конструкцию For Each) и организацию разбора случаев (конструкцию Select - Case).
  • На организацию цикла и разбор случаев тратится примерно равное время, чуть более секунды на организацию цикла и чуть менее секунды на организацию разбора случаев.
  • Внутренние операторы конструкции Case, связанные с выполнением встроенных функций Asc и Mid, занимают пренебрежимо малое время в сравнение со временем, требуемым для организации конструкций цикла и разбора случаев, не более 5% от времени работы конструкции Select.


  • Заменим теперь конструкцию:

    Select Case Sym Case "А" To "Я" <операторы1> Case "а" To "я" <операторы2> End Select на конструкцию: If Sym >= "А" And Sym <= "Я" Then <операторы1> ElseIf Sym >= "а" And Sym <= "я" Then <операторы1> End If

    Листинг 2.24.

    (html, txt)

    Результаты вычислений в этом случае показаны в следующей таблице:

    Таблица 2.2. Временной профиль работы макроса - конструкции For Each и If-Then-ElseN эксперимента123
    TAll - Общее время работы2,062,021,98
    TForEach - Время работы внешнего цикла для конструкции For Each1,971,931,89
    TSelect - Время работы конструкции If-Then-Else0,970,960,89
    TIndex1 - Время1 работы функции Asc0,0150,020,007
    TIndex2 - Время2 работы функции Asc000
    TSym1 - Время1 работы функции Mid0,0460,0510,02
    TSym2 - Время2 работы функции Mid000
    На данном примере трудно понять, какая конструкция работает эффективнее. Разброс значений соизмерим с погрешностью измерений. Я все-таки рекомендую применять конструкцию Select - Case в подобных ситуациях.

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

    Листинг 2.25.

    (html, txt)

    на обычный цикл:

    For i = 1 To Selection.Characters.Count Sym1 = Selection.Characters(i)

    Листинг 2.26.

    (html, txt)

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

    Таблица 2.2. Временной профиль работы макроса - конструкции For I =1 To N и SelectN эксперимента123
    TAll - Общее время работы106,53113,37115,84
    TForEach - Время работы внешнего цикла для конструкции For Each106,44113,27115,74
    TSelect - Время работы конструкции Select -Case0,1400,1480,187
    TIndex1 - Время1 работы функции Asc0,020,030,015
    TIndex2 - Время2 работы функции Asc000
    TSym1 - Время1 работы функции Mid0,0780,0150,054
    TSym2 - Время2 работы функции Mid000
    Заметьте, вместо одной секунды на выполнение того же цикла теперь ушло около двух минут. Когда я впервые узнал, что при работе с коллекциями время, затрачиваемое на организацию старого, доброго и привычного цикла For I = 1 To N на два порядка больше времени работы цикла For Each, сказать, что я был поражен, слишком слабо. До сих пор не могу найти объяснения этому факту. Тем не менее этот факт имел место и в Office 97, такая же ситуация сохраняется и в Office 2000. Поэтому всегда в своих программах используйте, где можно цикл For Each - от этого существенно зависит эффективность выполнения ваших программ.

    Инструментальная панель и кнопки

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

    увеличить изображение
    Рис. 2.1.  Инструментальная панель Buffers с кнопками
    Хочу обратить Ваше внимание на то, что спроектированные мной кнопки имеют значок и текст. В тех случаях, когда создается кнопка, являющаяся вариацией стандартной кнопки, разумно копировать значок стандартной кнопки и добавлять текст, указывающий специфику собственной кнопки. Именно так я и поступал, копируя для своих кнопок значки стандартных кнопок Copy и Paste.

    Копирование объекта

    До сих пор я говорил о копировании выделенного текста. И делал я это потому, что копирование текста это наиболее типичная задача, возникающая при работе с документом Word. Однако понятно, что реально в документе Word выделяется не текст, а некоторая область документа, - объект Range, если говорить в терминах объектов. Я напомню, что объект Range может быть устроен столь же сложно, как и сам документ, и, наряду с текстом, содержать самые разные компоненты, например, рисунки. Стандартная реализация Copy - Paste фактически работает именно с объектом Range. Давайте напишем и мы такую же реализацию. Вот как задается буфер и макросы в подобной реализации:
    'Буфер, позволяющий сохранять объект Public ObjectBuffer As Range
    Public Sub CopyObject() 'Этот макрос копирует выделенный объект в буфер Set ObjectBuffer = Selection.Range End Sub
    Public Sub PasteObject() 'Этот макрос выполняет операцию, обратную копированию. 'Объект из буфера вставляется в точку, заданную курсором. 'Поскольку объект может быть сложным и содержать, например, 'рисунки, то используется техника копирования через стандартный буфер! ObjectBuffer.Copy Selection.PasteSpecial
    End Sub
    Листинг 2.4.
    (html, txt)
    Реализация макросов в этом случае даже более проста, чем в предыдущем случае. Однако, заметьте, она построена на использовании возможностей стандартного буфера и таких мощных методов работы с ним, как методы Copy и PasteSpecial. Стоит обратить внимание на то, что побочным эффектом этой реализации является изменение содержимого буфера. Конечно, можно было бы запоминать и восстанавливать его содержимое, но вряд ли стоит этим заниматься, поскольку данная реализация вряд ли имеет преимущества по сравнению со стандартной реализацией. Так что из трех пар приведенных макросов, практическую пользу может иметь самая первая и самая простая пара макросов, работающих с простым текстом.

    Копирование текста и шрифта

    Я рассмотрю сейчас, как можно копировать в буфер не только текст, но и его шрифт. Макросы, которые будут построены, вряд ли стоит использовать на практике, но с учебной точки зрения их рассмотрение представляется полезным. Если необходимо сохранить в буфере не только текст, но и характеристики шрифта, которым этот текст записан, то буфер уже не может быть представлен простой строковой переменной. В подобных случаях, когда необходимо запоминать разнообразные характеристики выделенной области текстового документа, зачастую полезно определить пользовательский тип, задающий требуемые характеристики. Эти общим приемом я и воспользуюсь в данной достаточно простой ситуации. Вот как выглядит теперь определение буфера:
    'Буфер, сохраняющий текст и шрифт Public Type TextAndFont BufText As String BufFont As Font End Type
    Public TaFBuffer As TextAndFont
    Листинг 2.2.
    (html, txt)
    Как видите, вначале дано определение пользовательского типа, содержащего два поля для хранения текста и объекта класса Font. Сам буфер описывается переменной введенного типа TextAndFont.
    Чуть усложняются и тексты макросов, решающие задачу копирования и вставки:
    Public Sub CopyTextAndFont() 'Этот макрос копирует выделенный текст и шрифт в буфер Set TaFBuffer.BufFont = Selection.Font TaFBuffer.BufText = Selection.Text End Sub
    Public Sub PasteTextAndFont() 'Этот макрос выполняет операцию, обратную копированию
    'К сожалению, такое присваивание свойства Font 'для объекта Selection не проходит?! 'Selection.Font = TaFBuffer.BufFont 'Но можно присвоить свойства объекту Font Selection.Font.Name = TaFBuffer.BufFont.Name Selection.Font.Bold = TaFBuffer.BufFont.Bold Selection.Font.Italic = TaFBuffer.BufFont.Italic Selection.Font.Size = TaFBuffer.BufFont.Size 'Текст из буфера с указанными параметрами шрифта 'вставляется в точку, заданную курсором. Selection.Text = TaFBuffer.BufText End Sub
    Листинг 2.3.
    (html, txt)
    Первый из этих макросов не нуждается в особых комментариях. Объект Selection имеет наряду со свойством Text и свойство Font, возвращающее объект данного класса. Эти свойства и передаются в поля переменной, определяющей наш буфер. Казалось бы, что второй макрос должен быть симметричным, поскольку необходимо выполнить такое же присваивание, но в другую сторону. Однако объекты Range и Selection обладают одной особенностью, - их свойству Font нельзя присвоить объект класса Font. Можно, однако, задать характеристики этого объекта, чем я и воспользовался.
    Повторяю, этот пример интересен скорее, как программистский прием. Практического значения он не имеет, так как стандартная реализация Copy - Paste решает эту же задачу, обладая при этом дополнительными преимуществами. Заметьте, что эта пара макросов неявно предполагает, что выделенный текст записан одним шрифтом, параметры которого постоянны для всего текста. Если же это не так, то будут использованы параметры конечного участка текста. В то же время стандартная реализация при вставке текста будет сохранять все изменения шрифта выделенного участка текста, что, конечно, представляется более разумным.

    Копирование текста

    Вначале рассмотрим совсем простой случай и напишем два макроса, первый из которых запоминает выделенный текст в буфере, а второй - вставляет текст из буфера в точку вставки, заданную курсором. Для решения задачи необходимо понимать три вещи:
  • как представить буфер,
  • как запомнить в буфере выделенный текст,
  • как текст из буфера переносится в точку вставки.

  • Каждая из этих задач решается в одну строчку. Поэтому давайте взглянем на программный текст, а затем я приведу краткий комментарий:
    'Текстовый буфер задается обычной строкой Public TextBuffer As String
    Public Sub CopyText() 'Этот макрос копирует выделенный текст в буфер TextBuffer = Selection.Text End Sub
    Public Sub PasteText() 'Этот макрос выполняет операцию, обратную копированию 'Текст из буфера вставляется в точку, заданную курсором Selection.Text = TextBuffer End Sub
    Листинг 2.1.
    (html, txt)
    Как видите, буфер задается обычной текстовой переменной VBA. Выделенный текст задается свойством Text объекта Selection. Точка вставки, заданная курсором, также представляется объектом Selection.
    Несмотря на простоту этих макросов, я часто использую их наряду со стандартными реализациями Copy - Paste. Дело в том, что при вставке скопированного текста в новое местоположение всегда возникает вопрос, как должен быть отформатирован вставляемый текст (шрифт, размер, курсив и другие свойства), - должно ли использоваться форматирование копируемого текста или форматирование, определяемое контекстом точки вставки. В стандартной реализации при вставке используется форматирование копируемого текста, но во многих случаях предпочтительным является контекст точки вставки. В тех ситуациях, когда необходимо вставлять только текст, сохраняя особенности стиля точки вставки, наши простые макросы предпочтительнее стандартной реализации.

    Корректировка текста, набранного в "ошибочной" раскладке

    Замечу, что для решения этой задачи в Office 2000 введена специальная "интеллектуальная" функция, которая распознает ошибку переключения клавиатуры. К сожалению, она правильно работает лишь в тех случаях, когда текст абзаца набирался на одном языке, а затем по ошибке произошло переключение клавиатуры на другой язык. В тех же случаях, когда текст является двуязычным, и каждый абзац может содержать фрагменты текста, например, термины на другом языке, эта функция работает некорректно. Мне она не подходит, я ее отключаю и пользуюсь собственными макросами.
    При решении этой задачи я исходил из стандартной клавиатуры, имеющей 101 клавишу. Четыре ряда основных клавишей, используемых для набора русского и английского текста, содержат в двух регистрах 94 символа, не считая символов пробела и табуляции. Цифры и еще 10 символов одинаковы в обеих раскладках, а 74 символа нуждаются в трансляции, когда текст набран не в той раскладке.
    Перевод текста из английской раскладки в русскую
    Если русский текст набирается в английской раскладке, то при трансляции такого текста все буквы английского алфавита переходят в буквы русского алфавита. Но поскольку русских букв больше, то 14 небуквенных символов английской раскладки транслируются в русские буквы (7 в верхнем и 7 в нижнем регистрах). Кроме того, 7 символов, отличных от буквенно-цифровых, несовпадающие в разных раскладках, также должны быть переведены соответствующим образом.
    При написании макроса я сочетал приведенную выше схему трансляции для буквенных символов английского алфавита, имеющих плотную кодировку, и схему разбора случаев для оставшихся символов. Посимвольный разбор случаев нагляден, хотя и не эффективен. В данном случае на некоторую потерю эффективности можно пойти, поскольку макросы применяются, обычно, к сравнительно небольшим текстам. Пришлось также учесть три нюанса, связанных с автоматической коррекцией текста в процессе его набора.
    Первый из них таков. В английской раскладке русская буква "э" (большая и малая) задается кавычками - одинарными и двойными. Но тут-то и возникает закавыка, поскольку Word может автоматически заменять прямые кавычки, на другие "изящные" и угловые кавычки, причем кавычки могут быть как открывающие, так и закрывающие. Заменять прямые двойные кавычки могут четыре различные парные кавычки:
    Корректировка текста, набранного в

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

    Замечу, что для решения этой задачи в Office 2000 введена специальная "интеллектуальная" функция, которая распознает ошибку переключения клавиатуры. К сожалению, она правильно работает лишь в тех случаях, когда текст абзаца набирался на одном языке, а затем по ошибке произошло переключение клавиатуры на другой язык. В тех же случаях, когда текст является двуязычным, и каждый абзац может содержать фрагменты текста, например, термины на другом языке, эта функция работает некорректно. Мне она не подходит, я ее отключаю и пользуюсь собственными макросами.
    При решении этой задачи я исходил из стандартной клавиатуры, имеющей 101 клавишу. Четыре ряда основных клавишей, используемых для набора русского и английского текста, содержат в двух регистрах 94 символа, не считая символов пробела и табуляции. Цифры и еще 10 символов одинаковы в обеих раскладках, а 74 символа нуждаются в трансляции, когда текст набран не в той раскладке.
    Перевод текста из английской раскладки в русскую
    Если русский текст набирается в английской раскладке, то при трансляции такого текста все буквы английского алфавита переходят в буквы русского алфавита. Но поскольку русских букв больше, то 14 небуквенных символов английской раскладки транслируются в русские буквы (7 в верхнем и 7 в нижнем регистрах). Кроме того, 7 символов, отличных от буквенно-цифровых, несовпадающие в разных раскладках, также должны быть переведены соответствующим образом.
    При написании макроса я сочетал приведенную выше схему трансляции для буквенных символов английского алфавита, имеющих плотную кодировку, и схему разбора случаев для оставшихся символов. Посимвольный разбор случаев нагляден, хотя и не эффективен. В данном случае на некоторую потерю эффективности можно пойти, поскольку макросы применяются, обычно, к сравнительно небольшим текстам. Пришлось также учесть три нюанса, связанных с автоматической коррекцией текста в процессе его набора.
    Первый из них таков. В английской раскладке русская буква "э" (большая и малая) задается кавычками - одинарными и двойными. Но тут-то и возникает закавыка, поскольку Word может автоматически заменять прямые кавычки, на другие "изящные" и угловые кавычки, причем кавычки могут быть как открывающие, так и закрывающие. Заменять прямые двойные кавычки могут четыре различные парные кавычки:
    Корректировка текста, набранного в

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

    Public Sub FromEToR() 'Translation of Symbols: England --> Russian Const ALU = "ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ" Const AL = "фисвуапршолдьтщзйкыегмцчня"

    Dim Sym As String, Sym1 As Range Dim Index As Byte Dim Result As String Dim Pravka As Boolean Dim Pravka1 As Boolean Pravka = False Pravka1 = False Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 'Исправление ошибочной автокорректировки If Pravka And (Sym <> " ") Then Sym = LCase(Sym): Pravka = False Select Case Sym Case "A" To "Z" 'английская буква верхнего регистра Index = Asc(Sym) - Asc("A") + 1 Sym = Mid(ALU, Index, 1) Case "a" To "z" 'английская буква нижнего регистра Index = Asc(Sym) - Asc("a") + 1 Sym = Mid(AL, Index, 1) 'Символы, переходящие в символы Case "?": Sym = "," Case "/": Sym = "." Case "^": Sym = ":" Case "$": Sym = ";" Case "&": Sym = "?" Case "@": Sym = """" Case "#": Sym = "№" 'Символы, переходящие в буквы Case ",": Sym = "б" Case "<": Sym = "Б" Case ".": Sym = "ю" Case ">": Sym = "Ю" Case ";": Sym = "ж" Case ":": Sym = "Ж" Case "'": Sym = "э" Case """": Sym = "Э" Case "[": Sym = "х" Case "]": Sym = "ъ" Case "{": Sym = "Х" Case "}": Sym = "Ъ" Case "`": Sym = "ё" Case "~": Sym = "Ё" 'Другие виды кавычек Case Chr(145): Sym = "э" Case Chr(146): Sym = "э" Case Chr(147): Sym = "Э" Case Chr(148): Sym = "Э" Case Chr(171): Sym = "Э" Case Chr(187): Sym = "Э" Case Else: 'Кодировки совпадают End Select 'Обнаружение ошибочной автокорректировки If Sym = "," Then Pravka = True If Pravka1 And (Sym = " ") Then Pravka = True Else: Pravka1 = False End If If Sym = "ю" Then Pravka1 = True 'Формирование результата Result = Result + Sym Next Selection.LanguageID = wdRussian Selection.TypeText Result End Sub


    Листинг 2.18.

    Перевод текста из русской раскладки в английскую

    Макрос FromRToE, решающий обратную задачу по отношению к макросу FromEToR, похож на него в реализации. И здесь возникают некоторые проблемы, связанные с автокоррекцией кавычек. Обратите внимание также на запись строки AlU, задающей перевод русских букв в соответствующие буквы и символы в английской раскладке. Длина этой строки равна 32, а не 33, как может показаться с первого взгляда, поскольку две подряд идущие парные кавычки воспринимаются как один символ. Вот текст макроса:

    Public Sub FromRToE() 'Translation of Symbols: Russian --> England Const ALU = "FZ" Const AL = "f,dult;pbqrkvyjghcnea[wxio]sm'.z"

    Dim Sym As String, Sym1 As Range Dim Index As Byte Dim Result As String Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 Select Case Sym Case "А" To "Я" 'русская буква верхнего регистра Index = Asc(Sym) - Asc("А") + 1 Sym = Mid(ALU, Index, 1) Case "а" To "я" 'русская буква нижнего регистра Index = Asc(Sym) - Asc("а") + 1 Sym = Mid(AL, Index, 1) 'Символы, переходящие в символы Case "?": Sym = "&" Case ".": Sym = "/" Case ",": Sym = "?" Case ";": Sym = "$" Case "№": Sym = "#" Case ":": Sym = "^" Case """": Sym = "@" Case Chr(147): Sym = "@" Case Chr(148): Sym = "@" Case Chr(171): Sym = "@" Case Chr(187): Sym = "@" Case "ё": Sym = "`" Case "Ё": Sym = "~" Case Else: 'Кодировки совпадают End Select 'Устранение результатов автоматической правки текста Result = Result + Sym Next Selection.LanguageID = wdEnglishUS Selection.TypeText Result End Sub

    Листинг 2.19.

    Перевод кириллицы в латиницу. Макрос FromRuToLat

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

    По своей реализации макрос FromRuToLat ближе всего соответствует общей схеме:


    Public Sub FromRuToLat() 'Translation of Symbols: Russian --> Latin Dim ALU( 1 To 32) As String ALU(1) = "A": ALU(2) = "B": ALU(3) = "V": ALU(4) = "G" ALU(5) = "D": ALU(6) = "E": ALU(7) = "J": ALU(8) = "Z" ALU(9) = "I": ALU(10) = "I": ALU(11) = "K": ALU(12) = "L" ALU(13) = "M": ALU(14) = "N": ALU(15) = "O": ALU(16) = "P" ALU(17) = "R": ALU(18) = "S": ALU(19) = "T": ALU(20) = "U" ALU(21) = "F": ALU(22) = "H": ALU(23) = "C": ALU(24) = "Ch" ALU(25) = "Sh": ALU(26) = "Sch": ALU(27) = "'": ALU(28) = "Y" ALU(29) = "'": ALU(30) = "E": ALU(31) = "Yu": ALU(32) = "Ya"

    Dim Sym As String, Sym1 As Range Dim Index As Byte Dim S As String Dim Result As String Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 Sym = UCase(Sym) Select Case Sym Case "А" To "Я" ' буква верхнего регистра Index = Asc(Sym) - Asc("А") + 1 S = ALU(Index) If Sym <> Sym1 Then S = LCase(S) 'Символ в нижнем регистре Sym = S Case "Ё" S = "E" If Sym <> Sym1 Then S = LCase(S) 'Символ в нижнем регистре Sym = S Case Else 'Кодировки совпадают Sym = Sym1 End Select Result = Result + Sym Next Selection.TypeText Result End Sub

    Листинг 2.20.

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

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


    Sub RepNew() ' Эта процедура преобразует выделенный программный текст 'Заменяя пробелы табуляцией и конец абзаца мягким концом строки Dim MyRange As Range, TxtRange As String Dim StrFind As String, strReplace As String Debug.Print Val(vbCrLf), Val(vbLf) Set MyRange = Selection.Range TxtRange = MyRange.Text 'Замена концов абзаца StrFind = vbCr 'Chr(13) - Конец абзаца strReplace = vbVerticalTab 'Chr(11) - Разрыв строки TxtRange = Replace(TxtRange, StrFind, strReplace) 'Замена пробелов табуляцией StrFind = " " '4 пробела strReplace = vbTab 'символ табуляции TxtRange = Replace(TxtRange, StrFind, strReplace)

    StrFind = " " '3 пробела strReplace = vbTab 'символ табуляции TxtRange = Replace(TxtRange, StrFind, strReplace)

    StrFind = " " '2 пробела strReplace = vbTab 'символ табуляции TxtRange = Replace(TxtRange, StrFind, strReplace)

    MyRange.Text = TxtRange 'Замена стиля на стиль "Listing", если он встроен Dim MyStyle As Style For Each MyStyle In ActiveDocument.Styles If MyStyle.NameLocal = "Listing" Then MyRange.Style = "Listing" Next MyStyle

    End Sub

    Листинг 2.21.

    Макрос перекодировки

    Рассмотрим теперь макрос, который занимается настоящей перекодировкой. Как-то я получил от своего приятеля, работающего теперь за рубежом, письмо по Email в кириллице, но в кодировке, не распознаваемой в Outlook. Поскольку письмо начиналось с обращения ко мне по имени и отчеству, то раскодировать его вручную не представляло особого труда, хотя и потребовало времени. Работа по определению кода всегда представляет некоторый интерес, вспомните Шерлока Холмса в "Пляшущих человечках". Но, когда я получил второе письмо в той же кодировке, то я предпочел написать макрос, на что потребовалось гораздо меньше времени, чем на расшифровку этого письма по известному коду. Текст этого макроса реализован в полном соответствии с рассмотренной общей схемой:
    Public Sub CodeDA() 'Кодировка Rus -> Rus Const ALU = "бвчздецъйклмнопртуфхжигюыэящшьас" Const AL = "БВЧЗДЕЦЪЙКЛМНОПРТУФХЖИГЮЫЭЯЩШЬАС" Dim Sym As String, Sym1 As Variant Dim Index As Integer Dim Result As String Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 Select Case Sym Case "А" To "Я" 'русская буква верхнего регистра Index = Asc(Sym) - Asc("А") + 1 Sym = Mid(ALU, Index, 1) Case "а" To "я" 'английская буква нижнего регистра Index = Asc(Sym) - Asc("а") + 1 Sym = Mid(AL, Index, 1) End Select Result = Result + Sym Next Sym1 Selection.LanguageID = wdRussian Selection.TypeText Result
    End Sub
    Листинг 2.22.
    (html, txt)
    Заметьте, здесь речь идет о кодировке внутри русского алфавита, когда одни символы кодируются другими.

    Множественный буфер

    Долгожданной новинкой, появившейся в Office 2000, было введение буфера, в котором может храниться не единственное значение, а множество значений. Теперь буфер позволяет одновременно хранить до 16 различных объектов, что, несомненно, бывает крайне полезным в работе пользователя, когда часто приходится использовать в тексте повторяющиеся термины, фразы, рисунки.
    Конечно, всегда появляется желание, что-либо улучшить. Я бы, например, предпочел иметь одновременно оба буфера - одиночный для одноразовых работ и множественный для тех объектов, которые стоит запоминать на достаточно долгий срок. Иногда мне не хватает 16 объектов для долговременного хранения, и хотелось бы расширить объем множественного буфера. Хотелось бы также иметь возможность удалять некоторые объекты из буфера, надобность хранения которых отпала. В общем, причин, по которым может возникнуть потребность в написании собственной реализации множественного буфера может быть много. Да и задача эта интересна с программистской точки зрения. Давайте напишем собственную реализацию множественного буфера.
    Как всегда, нам нужно будет решить три вопроса: как представить такой буфер, как добавлять в него элементы и как переносить нужный элемент из буфера в точку вставки. Для множественного буфера возникают и новые задачи - чистка буфера, выделение нужного элемента, удаление отдельных элементов.
    Начнем с рассмотрения вопроса о представлении множественного буфера. Понятно, что единственной переменной теперь не обойтись и потребуется некоторая структура данных. Если ограничиться буфером с фиксированным числом элементов, как это сделано в стандартной реализации, то наиболее подходящим вариантом был бы массив. Но я предпочитаю в подобных случаях динамические структуры данных, где можно не задумываться о размерах буфера и не вводить, тем самым, лишних ограничений. В VBA есть отличная динамическая структура данных - коллекция. Ею я и воспользуюсь для представления буфера. Вот как выглядит раздел объявлений глобальных переменных модуля MultBufferModule, в котором я буду размещать все процедуры, связанные с решением нашей задачи:

    Листинг 2.5.

    (html, txt)

    Переменная MultBuffer задает множественный буфер, а переменные Elem и NumElem мне понадобятся для работы с элементами, хранящимися в буфере.

    Основной вопрос, который предстоит решить, как показать пользователю, элементы, хранящиеся в буфере? Очевидно, это должен быть некоторый список элементов. Но каково должно быть содержимое элементов, предъявляемых пользователю для показа? Проблема в том, что объекты, хранящиеся в буфере, могут быть достаточно сложными - длинные тексты, рисунки, Ole-объекты, комбинация текста и рисунков и так далее. Я выберу решение этой задачи, подсказанное стандартной реализацией, - там, где объект содержит текст, пользователю будет предъявлен начальный участок этого текста. Для графических объектов и им подобным - элементам документа Word, входящим в коллекции Shapes и InlineShapes, пользователю будет предъявлено слово "ОбъектN", где N будет задавать номер такого объекта.

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

    Другое возможное решение состоит в том, чтобы для отображения списка использовать панель инструментов со специальными кнопками, в частности, иметь на панели кнопку класса ComboBox типа DropDown. Эта кнопка также позволяет динамически формировать список ее элементов и задавать реакцию на выбор определенного элемента из списка. О работе с такими кнопками я расскажу в следующей лекции. Сейчас мы рассмотрим оба варианта реализации, поскольку оба они интересны с программистской точки зрения.

    Преобразование данных справочника "Кто есть кто" в контакты Outlook

    Приведу точную постановку задачи. Дан справочник персон. Необходимо дать возможность пользователю выбрать из общего списка интересующих его персон, всю возможную информацию о них перенести в базу данных приложения Outlook, в его папку "Контакты". Попутно для каждого из введенных контактов создать ежегодно повторяющееся событие с выдачей своевременного предупреждения о дне рождения контакта. Как видите, задача осложняется тем, что нам придется иметь дело не только с одним приложением Word, необходимо будет обеспечить взаимодействие с приложением Outlook, программно работать с его объектами. Но я предупреждал, что последний пример будет более сложным. Несмотря на то, что объекты Outlook еще не описаны, пример работы с такими объектами, мне кажется, представляет интерес и в данном месте. Что касается первой части этой задачи, собственно работы с документом Word, представляющим справочник, то хотелось бы отметить, что эта задача в тех или иных вариациях возникает при работе со многими подобными документами. Например, из справочника, задающего список товаров, необходимо выбрать нужные товары и сформировать заказ на их покупку.
    Структура справочника "Кто есть кто", подготовленного фирмой Dator
    Еще раз скажу, что в этой задаче я работал с вполне конкретным справочником, созданным не мной, а подготовленным фирмой Dator. Справочник этот опубликован, если не ошибаюсь, в 1998 году. Так что мне пришлось обрабатывать вполне реальную информацию, подготовленную для этого справочника самими персонами, фамилии которых вошли в справочник. Понятно, что эти персоны, будучи выдающимися личностями, не всегда выдерживали требования к структуре представляемой ими информации, что придавало прелесть программе, обрабатывающей эту информацию.
    Данный справочник представляет организованную в алфавитном порядке совокупность записей о личностях российского компьютерного бизнеса. Чтобы придать структуру справочнику, использовались три стандартных способа структуризации текста. Скажу о них еще раз:
  • Прежде всего, используется синтаксическая структура документа Word. Известно, что текст любого документа Word структурирован, - в документе можно выделить поддокументы, разделы, абзацы, предложения, слова.
  • Другой общий способ структуризации состоит в разделении текста на части, главы, параграфы, подпараграфы за счет введения стилей и, прежде всего, заголовков соответствующего уровня. Очень часто создается специальная совокупность стилей для придания нужной структуры текстовому документу.
  • Третий способ организации данных справочника состоит в использовании специальных ключевых слов для выделения тех или иных разделов и приданию документу тем самым нужной структуры.


  • Приведу точную постановку задачи. Дан справочник персон. Необходимо дать возможность пользователю выбрать из общего списка интересующих его персон, всю возможную информацию о них перенести в базу данных приложения Outlook, в его папку "Контакты". Попутно для каждого из введенных контактов создать ежегодно повторяющееся событие с выдачей своевременного предупреждения о дне рождения контакта. Как видите, задача осложняется тем, что нам придется иметь дело не только с одним приложением Word, необходимо будет обеспечить взаимодействие с приложением Outlook, программно работать с его объектами. Но я предупреждал, что последний пример будет более сложным. Несмотря на то, что объекты Outlook еще не описаны, пример работы с такими объектами, мне кажется, представляет интерес и в данном месте. Что касается первой части этой задачи, собственно работы с документом Word, представляющим справочник, то хотелось бы отметить, что эта задача в тех или иных вариациях возникает при работе со многими подобными документами. Например, из справочника, задающего список товаров, необходимо выбрать нужные товары и сформировать заказ на их покупку.
    Структура справочника "Кто есть кто", подготовленного фирмой Dator
    Еще раз скажу, что в этой задаче я работал с вполне конкретным справочником, созданным не мной, а подготовленным фирмой Dator. Справочник этот опубликован, если не ошибаюсь, в 1998 году. Так что мне пришлось обрабатывать вполне реальную информацию, подготовленную для этого справочника самими персонами, фамилии которых вошли в справочник. Понятно, что эти персоны, будучи выдающимися личностями, не всегда выдерживали требования к структуре представляемой ими информации, что придавало прелесть программе, обрабатывающей эту информацию.
    Данный справочник представляет организованную в алфавитном порядке совокупность записей о личностях российского компьютерного бизнеса. Чтобы придать структуру справочнику, использовались три стандартных способа структуризации текста. Скажу о них еще раз:
  • Прежде всего, используется синтаксическая структура документа Word. Известно, что текст любого документа Word структурирован, - в документе можно выделить поддокументы, разделы, абзацы, предложения, слова.
  • Другой общий способ структуризации состоит в разделении текста на части, главы, параграфы, подпараграфы за счет введения стилей и, прежде всего, заголовков соответствующего уровня. Очень часто создается специальная совокупность стилей для придания нужной структуры текстовому документу.
  • Третий способ организации данных справочника состоит в использовании специальных ключевых слов для выделения тех или иных разделов и приданию документу тем самым нужной структуры.



  • В данном конкретном справочнике применяется все три способа структуризации. Начало каждой записи выделяется соответствующим стилем, именованным "Заголовок2". Этот заголовок содержит фамилию и, как правило, имя и отчество личности, включенной в справочник.

    Записи справочника содержат определенные поля:

  • Должность,
  • Дату рождения,
  • Адрес,
  • Адрес электронной почты,
  • Телефон,
  • Факс,
  • Увлечения,
  • Разное.


  • Некоторые из этих полей обязательны и имеют фиксированное расположение. Так поле "Должность" занимает отдельный абзац, следующий непосредственно за заголовком. Положение остальных полей произвольно, они расположены, как правило, в отдельных абзацах и выделяются ключевыми словами, такими как "адрес", "e-mail" и другими. Абзацы позволяют найти очередное поле, а ключевые слова позволяют определить тип этого поля.

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

    АГАМИРЗЯН Игорь Рубенович

    ЗАО "Майкрософт", руководитель отдела.

    Отвечает за проектную работу с крупными заказчиками.

    Родился 21 марта 1957 года в Ленинграде. В 1979 году окончил мехмат ЛГУ, до 1992 года работал в АН СССР, одновременно до 1995 года преподавал в СПб Техническом университете, кандидат физико-математических наук, старший научный сотрудник, доцент. В 1991 году принял участие в создании компании "АстроСофт" и до 1995 года являлся техническим директором этой компании.


    В 1993 году начал сотрудничать с Microsoft, с 1995 года является штатным сотрудником Microsoft Consulting Services. В 1996 году возглавляет российское отделение MCS. Имеет статус "Microsoft Certified Systems Engineer". Неоднократно попадал в различные опросы и рейтинги, в том числе в Дейтор top100 '94, "Кто есть кто в компьютерном мире Петербурга" 95 и 96 года. С 1993 года входит в Marquis "Who's Who in Science and Engineering" и "Who's Who in the World".

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

    Основным своим достижением в компьютерном бизнесе считает участие в создании питерской фирмы "АстроСофт", оказавшейся на удивление живучей.

    Любимый способ проведения досуга отсутствует в связи с отсутствием досуга, как такового.

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

    Адрес: 125190, Москва, Чапаевский пер., 14, Microsoft.

    Тел.: (095) 967-85-85

    Факс: (095) 967-85-00

    e-mail: IgorA@Microsoft.com

    АЛЬТШУЛЕР Игорь Григорьевич

    Фирма "Куб" (Н.Новгород), вице-президент, Первая Нижегородская гильдия профессиональных консультантов, вице-президент, еженедельник PC Week/RE, обозреватель, еженедельник "Биржа" (Н.Новгород), обозреватель.

    Родился 28 сентября 1954 года в Горьком. По гороскопу - Весы, старается соответствовать.

    В 1971 году окончил с отличием мехмат Горьковского университета. Работал прикладным и системным программистом, был начальником отдела автоматизации крупного проектного института. Имеет авторское свидетельство на изобретение, сертификат фирмы McDonnell Douglas (США). 1991-1993 гг. - директор по развитию АО "Диалог-Н.Новгород", 1993-1995 гг. - советник президента страховой компании "Утес", с 1995 года - независимый консультант и аналитик. В 1994 году проходил стажировку в США. Автор и соавтор нескольких популярных книг, связанных с обработкой текстов, электронными таблицами, применением компьютеров в экономике, Интернетом.


    Основными достижениями последних лет считает "Консалтинг-бал", итоги которого были опубликованы в PC Week/RE, ряд "круглых столов".

    Увлечение - стихи (пишет, читает "про себя" чужие и свои, читает вслух свои и чужие).

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

    Умеет читать и писать по-английски, может поддержать светскую беседу. В качестве синхронного переводчика смотрится много хуже.

    Тел.: (831-2) 94-20-55

    Адрес: 603123, Нижний Новгород, А-123, а/я 176, Альтшулеру И.Г.

    e-mail: altsh@kis.ru

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

    on_load_lecture()

    Преобразование данных справочника
    Преобразование данных справочника
    Дальше "

    Преобразование данных справочника
    Если Вы заметили ошибку - сообщите нам.
    Преобразование данных справочника
    Страницы:

    " |

    1

    |

    2

    |

    3

    |

    4

    |

    5

    |

    6

    |

    7

    |

    вопросы | "

    |

    для печати и PDA

    Преобразование данных справочника
    Преобразование данных справочника
    Преобразование данных справочника
    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование



    В данном конкретном справочнике применяется все три способа структуризации. Начало каждой записи выделяется соответствующим стилем, именованным "Заголовок2". Этот заголовок содержит фамилию и, как правило, имя и отчество личности, включенной в справочник.

    Записи справочника содержат определенные поля:

  • Должность,
  • Дату рождения,
  • Адрес,
  • Адрес электронной почты,
  • Телефон,
  • Факс,
  • Увлечения,
  • Разное.


  • Некоторые из этих полей обязательны и имеют фиксированное расположение. Так поле "Должность" занимает отдельный абзац, следующий непосредственно за заголовком. Положение остальных полей произвольно, они расположены, как правило, в отдельных абзацах и выделяются ключевыми словами, такими как "адрес", "e-mail" и другими. Абзацы позволяют найти очередное поле, а ключевые слова позволяют определить тип этого поля.

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

    АГАМИРЗЯН Игорь Рубенович

    ЗАО "Майкрософт", руководитель отдела.

    Отвечает за проектную работу с крупными заказчиками.

    Родился 21 марта 1957 года в Ленинграде. В 1979 году окончил мехмат ЛГУ, до 1992 года работал в АН СССР, одновременно до 1995 года преподавал в СПб Техническом университете, кандидат физико-математических наук, старший научный сотрудник, доцент. В 1991 году принял участие в создании компании "АстроСофт" и до 1995 года являлся техническим директором этой компании.


    В 1993 году начал сотрудничать с Microsoft, с 1995 года является штатным сотрудником Microsoft Consulting Services. В 1996 году возглавляет российское отделение MCS. Имеет статус "Microsoft Certified Systems Engineer". Неоднократно попадал в различные опросы и рейтинги, в том числе в Дейтор top100 '94, "Кто есть кто в компьютерном мире Петербурга" 95 и 96 года. С 1993 года входит в Marquis "Who's Who in Science and Engineering" и "Who's Who in the World".

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

    Основным своим достижением в компьютерном бизнесе считает участие в создании питерской фирмы "АстроСофт", оказавшейся на удивление живучей.

    Любимый способ проведения досуга отсутствует в связи с отсутствием досуга, как такового.

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

    Адрес: 125190, Москва, Чапаевский пер., 14, Microsoft.

    Тел.: (095) 967-85-85

    Факс: (095) 967-85-00

    e-mail: IgorA@Microsoft.com

    АЛЬТШУЛЕР Игорь Григорьевич

    Фирма "Куб" (Н.Новгород), вице-президент, Первая Нижегородская гильдия профессиональных консультантов, вице-президент, еженедельник PC Week/RE, обозреватель, еженедельник "Биржа" (Н.Новгород), обозреватель.

    Родился 28 сентября 1954 года в Горьком. По гороскопу - Весы, старается соответствовать.

    В 1971 году окончил с отличием мехмат Горьковского университета. Работал прикладным и системным программистом, был начальником отдела автоматизации крупного проектного института. Имеет авторское свидетельство на изобретение, сертификат фирмы McDonnell Douglas (США). 1991-1993 гг. - директор по развитию АО "Диалог-Н.Новгород", 1993-1995 гг. - советник президента страховой компании "Утес", с 1995 года - независимый консультант и аналитик. В 1994 году проходил стажировку в США. Автор и соавтор нескольких популярных книг, связанных с обработкой текстов, электронными таблицами, применением компьютеров в экономике, Интернетом.


    Основными достижениями последних лет считает "Консалтинг-бал", итоги которого были опубликованы в PC Week/RE, ряд "круглых столов".

    Увлечение - стихи (пишет, читает "про себя" чужие и свои, читает вслух свои и чужие).

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

    Умеет читать и писать по-английски, может поддержать светскую беседу. В качестве синхронного переводчика смотрится много хуже.

    Тел.: (831-2) 94-20-55

    Адрес: 603123, Нижний Новгород, А-123, а/я 176, Альтшулеру И.Г.

    e-mail: altsh@kis.ru

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

    Общий план решения задачи

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

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


  • И я начну с того, что приведу описание глобальных переменных, которые будут использоваться в процессе решения задачи. Эти переменные описаны в модуле Tool, в который и будут помещены все основные процедуры, требуемые для решения задачи.

    Option Explicit 'Объект Outlook и его компоненты Public myOl As Outlook.Application, olNameSpace As NameSpace 'Коллекция избранных личностей Public CollectionOfPersons As New Collection 'Коллекция номеров абзацев, задающих начало записей Public Numbers As New Collection Public Con As New Collection 'Определение типа - записи, характеризующей личность Public Type Person FirstName As String LastName As String MiddleName As String Post As String DOB As Date Address As String Tel As String Email As String Fax As String Other As String End Type

    Листинг 2.27.

    Нам потребовалось описать объект Outlook и связанный с ним объект NameSpace, задающий пространство имен. Описание этих объектов необходимо для обеспечения взаимодействия двух приложений, с тем, чтобы мы могли в проекте документа Word работать с объектами Outlook. Коллекция CollectionOfPersons содержит фамилии персон, входящих в справочник. Две следующие коллекции Numbers и Con носят вспомогательный характер, но важный для общего понимания алгоритма решения. Первая из них содержит номера абзацев, начинающих описание персоны, вторая - порядковые номера персон, отобранных пользователем. Создание этих коллекций позволяет избежать лишних просмотров полного текста документа и получать доступ непосредственно к нужному абзацу. Наконец, в разделе общих объявлений дано определение пользовательского типа Person, формально описывающего запись и поля этой записи, которые будут заполняться в процессе анализа записанной информации, а затем будут переноситься в поля контакта Outlook.

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


    Public Sub WTOOL() 'Процедура преобразует справочник персоналий ' в базу данных Контакты приложения Outlook

    'Формирование списка персоналий и выбор пользователя Call FormList 'Создание записей для избранных Call SelectPerson End Sub

    Листинг 2.28.

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

    Создание списка персоналий

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

    Public Sub FormList() 'Эта процедура формирует список личностей Dim par As Paragraph, parStyle As String Dim i As Integer, n As Integer With ActiveDocument parStyle = .Paragraphs(1).Style n = ActiveDocument.Paragraphs.Count i = 1 For Each par In .Paragraphs If par.Style = parStyle Then 'Добавить элемент в список и номер абзаца в коллекцию frmPersons.lstPersons.AddItem par.Range.Text Numbers.Add i End If i = i + 1 Next par End With frmPersons.Show End Sub


    Листинг 2.29.

    Как видите, процедура, полностью решающая задачу, получилась довольно короткая и простая. Все это, конечно, благодаря тем возможностям, которые предоставляет Word для этих целей. В цикле (конечно же, For Each) по всем абзацам текста получаем очередной абзац - объект par. Далее существенно используется тот факт, что информация о каждой персоне начинается с нового абзаца, имеющего специальный стиль, в данном случае, задаваемый переменной parStyle, характерный только для таких абзацев. Используется также и тот факт, что этот абзац содержит только фамилию, имя и отчество персоны.

    Затем создаются элементы списка формы. Нетрудно догадаться, что frmPersons - это имя формы, lstPersons - имя списка (элемента управления ListBox) этой формы. Хочу обратить внимание, на то, что по ходу дела создается коллекция Numbers с номерами абзацев, начинающих информацию об очередной персоне. Эта информация получается почти бесплатно, но в дальнейшем существенно сократит время работы, позволив лишний раз не проходить по всему документу. Последний оператор этой процедуры показывает на экране форму с заполненным списком фамилий персон справочника. Вот как выглядит эта форма в процессе работы с ней:

    Преобразование данных справочника

    Рис. 2.4.  Форма, содержащая список персон справочника "Кто есть кто"

    Создание коллекции "избранных" персон

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

    Private Sub cmdSelectPerson_Click() Dim intLoop As Integer, intSelect As Integer Dim strSelect As String Dim ВыборСделан As Boolean Dim Num As Integer ВыборСделан = False intLoop = 0 intSelect = 0 'Поиск выделенных элементов Do If frmPersons.lstPersons.Selected(intLoop) Then 'Найден очередной элемент strSelect = frmPersons.lstPersons.List(intLoop) Num = intLoop + 1 ВыборСделан = True intSelect = intSelect + 1 CollectionOfPersons.Add strSelect Con.Add Num End If intLoop = intLoop + 1 Loop Until intLoop = frmPersons.lstPersons.ListCount If ВыборСделан Then Unload Me Set myOl = CreateObject("Outlook.Application") Else MsgBox ("Выбор не сделан") End If 'Печать коллекции 'For Each pers In CollectionOfPersons ' Debug.Print pers 'Next pers End Sub


    Листинг 2.30.

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

    Создание записи Person

    Нам предстоит теперь разобраться с более сложными вопросами. В той части, которая связана с обработкой текстового документа, предстоит понять, как выделить из текста нужную информацию о персоне, для того чтобы создать формальный объект (переменную) созданного нами ранее пользовательского типа Person, и заполнить поля этого объекта. Другая часть работы связана с созданием объекта Outlook - элемента папки Contacts. Давайте посмотрим, как все это можно реализовать. Вот текст процедуры SelectPerson:

    Public Sub SelectPerson()

    ' Выделение записи Dim par As Paragraph, CountPar As Integer Dim ibeg As Integer, ifin As Integer, nPerson As Integer Dim PersonRange As Range Dim i As Integer, Num As Variant With ActiveDocument Set PersonRange = .Paragraphs(1).Range i = 0 'Цикл по записям, отобранных пользователем For Each Num In Con i = i + 1 'Выделение области документа, занятой записью 'Номер абзаца, начинающего запись ibeg = Numbers(Num) 'Номер абзаца, заканчивающего запись ifin = Numbers(Con(i) + 1) - 1 PersonRange.Start = .Paragraphs(ibeg).Range.Start PersonRange.End = .Paragraphs(ifin).Range.End 'Выделение записи PersonRange.Select 'Обработать запись - объект Selection Call WorkWithSelected Next End With myOl.Quit


    End Sub

    Листинг 2.31.

    Внешний цикл организован по элементам коллекции Con, элементов у которой ровно столько, сколько персон выделил пользователь для преобразования информации о них в контакты папки Outlook. Первая возникающая задача для каждого элемента этого списка состоит в том, чтобы выделить область текстового документа, в которой записана информация о соответствующей персоне. Чтобы задать эту область - объект Range, достаточно знать параметры Start и End, определяющие местоположение начала и конца области. Вот как можно их определить. Я напомню, что текущий элемент Num коллекции Con задает порядковый номер персоны в списке, тогда по определению коллекции Number номер первого абзаца будет задаваться выражением Number(Num). Номер последнего абзаца записи можно определить, как номер первого абзаца следующей записи, уменьшенный на единицу. Этим алгоритмом я и пользуюсь в процедуре. Заметьте, если не принять дополнительных мер предосторожности, то он приведет к ошибке, когда выбрана последняя запись справочника. Чтобы избежать этого, я использовал стратегию, называемую введением "барьера", добавив в справочник специальную служебную запись (барьер) "Конец записей". Эта запись информирует пользователя об окончании списка персон и, естественно, никогда не будет входить в его выбор. Тем самым удается достаточно просто получить объект Range, задающий фрагмент текстового документа, описывающий информацию о нужной персоне. Выделение этой области задает объект Selection, с которым продолжает работу вызываемая процедура WorkWithSelected. Прежде, чем обсуждать ее работу, приведу ее текст:

    Public Sub WorkWithSelected() 'Обработка с выбранной и отмеченной записью Dim pers As Person, pars As Paragraphs, par As Paragraph Dim i As Integer, n As Integer Dim myR As Range Dim FirstWord As String Set pars = Selection.Paragraphs With pers 'Обработка первого абзаца - фамилии Set par = pars(1) Set myR = par.Range .FirstName = myR.Words(2).Text .MiddleName = myR.Words(3).Text .LastName = myR.Words(1).Text 'Обработка должности - следующего непустого абзаца Set par = pars(2) If par.Range.Words.Count = 1 Then Set par = pars(3) Set myR = par.Range n = myR.Words.Count myR.End = par.Range.Words(n - 1).End .Post = myR.Text 'Обработка оставшихся абзацев For Each par In pars Set myR = par.Range n = myR.Words.Count FirstWord = myR.Words(1).Text Select Case FirstWord Case "Родился ", "Родилась " .DOB = SelectDate(par.Range) Case "Тел" myR.Start = par.Range.Words(3).Start myR.End = par.Range.Words(n - 1).End .Tel = myR.Text Case "Факс" myR.Start = par.Range.Words(3).Start myR.End = par.Range.Words(n - 1).End .Fax = myR.Text Case "Адрес" myR.Start = par.Range.Words(3).Start myR.End = par.Range.Words(n - 1).End .Address = myR.Text Case "e" myR.Start = par.Range.Words(5).Start myR.End = par.Range.Words(n - 1).End .Email = myR.Text Case Else .Other = .Other + myR.Text End Select Next par If Not IsDate(.DOB) Then .DOB = "1 января " 'Debug.Print Selection.Range.Text 'Debug.Print .FIO, .Post, .Address, .DOB, .Tel, .Fax, .Email, .Other End With 'Запись создана - теперь создается контакт в Outlook Call WriteToContact(pers) End Sub


    Листинг 2.32.

    В этой процедуре заполняются поля переменной pers пользовательского типа Person. При этом существенно используются принятые соглашения о структуре справочника. Так по предположению первый абзац содержит только информацию о фамилии, имени и отчестве персоны, так что анализ первых трех слов этого абзаца позволяет заполнить поля LastName, FirstName и SecondName записи pers. Следующий абзац, который может следовать сразу за первым или быть отделенным пустым абзацем, содержит информацию о должности персоны. Запоминается весь текст этого абзаца, который и определяет описание должности персоны. Заметьте техническую деталь, при формировании данного поля из коллекции Words, задающей слова абзаца, удаляется последнее слово, в котором записан символ конца абзаца. Это же правило применяется и при работе с другими полями. При заполнении других полей не предполагается жесткий порядок их следования в тексте документа. Распознавание идет по ключевым словам, начинающим абзац, и программно осуществляется разбором случаев. Так заполнялись поля, определяющие телефон, факс, адрес и другие, подобные им. Все абзацы, не содержащие заданных ключевых слов, составляли поле Other. Пожалуй, наибольшую трудность вызывает распознавание даты рождения персоны. Дело в том, что для записи даты используются различные форматы, сокращения и прочие особенности. Более того, некоторые из персон, в особенности женщины, предпочитали не указывать год рождения, а мужчины не считали необходимым указывать число и месяц рождения. Чтобы справиться, хотя бы частично, с возникающими проблемами, я написал отдельную процедуру, занимающуюся разбором даты рождения. Вот ее текст:

    Public Function SelectDate(ran As Range) As String Dim Dat As String With ran If .Words(3) = "февраля " Then .Words(3) = "фев " Dat = .Words(2) & .Words(3) & .Words(4) If IsDate(Dat) Then SelectDate = Dat Else Dat = .Words(2) & .Words(3) If IsDate(Dat) Then SelectDate = Dat Else Dat = "1 января " & .Words(3) If IsDate(Dat) Then SelectDate = Dat Else Dat = "1 января " End If End If End If


    End With End Function

    Листинг 2.33.

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

    ?IsDate("13 февраля 1961") False ?IsDate("13 фев. 1961") True

    Листинг 2.34.

    В этой процедуре я не старался исправить все возможные ошибки, скорее я проверял корректность той или иной комбинации, используя для проверки функцию IsData, возвращающую истину, когда ее аргумент является правильной датой с точки зрения Office 2000. Если же установить дату рождения не удавалось, то в качестве даты принималась некоторая условная дата (1 января текущего года), что позволяло позже при работе с контактом понимать, что точная дата рождения контакта не известна.

    По завершении формирования записи pers эта запись в качестве аргумента передавалась при вызове процедуры WriteContact, в которой и реализована работа с объектами Outlook.

    Контакты и другие объекты Outlook

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

    Public Sub WriteToContact(pers As Person) 'Создание нового контакта в Outlook и запись данных о нем Dim newContact As ContactItem Set newContact = myOl.CreateItem(olContactItem) 'newContact.Display With newContact .BirthDay = pers.DOB .FirstName = pers.FirstName .MiddleName = pers.MiddleName .LastName = pers.LastName .BusinessFaxNumber = pers.Fax .BusinessTelephoneNumber = pers.Tel .BusinessAddress = pers.Address .Email1Address = pers.Email .JobTitle = pers.Post .Save End With ' newContact.Save 'Включить предупреждение о дне рождения Dim myFolder As MAPIFolder Dim newAppointment As Object Set olNameSpace = myOl.GetNamespace("MAPI") Set myFolder = olNameSpace.GetDefaultFolder(olFolderCalendar) Set newAppointment = myFolder.Items(myFolder.Items.Count) With newAppointment .ReminderSet = True .ReminderMinutesBeforeStart = 1440 .Save End With


    End Sub

    Листинг 2.35.

    Начну с нескольких общих замечаний. Прежде всего, хочу успокоить, что мои запугивания насчет сложности данной задачи, работающей с объектами Outlook, являлись лишь обычной "страшилкой" для начинающих. Ничего сложного в работе с объектами Outlook, также как и с другими объектами нет, нужно только с ними познакомиться. Истины ради, следует сказать, что объектная модель Outlook существенно отличается по принципам построения от объектной модели таких приложений, как Word и Excel. Например, на верхнем уровне нет привычных коллекций, - вместо них используются объекты - папки (Folders). Соответственно нет метода Add, создающего новый элемент и добавляющего его в коллекцию. Вместо этого элемент создается методом CreateItem и добавляется в папку при выполнении метода Save, при этом тип папки, в которую попадает элемент, определяется по типу созданного элемента. Имеются некоторые тонкости в способе получения той или иной папки. Поскольку у самого объекта Outlook.Application нет на верхнем уровне коллекции Folders то, для того чтобы добраться в Outlook до соответствующей папки, нужно вначале получить дополнительный объект olNameSpace класса NameSpace. И уже этот объект позволяет добраться до нужной папки. Все это не то, чтобы сложно, но нет ощущения интуитивной ясности и простоты.

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

    Чтобы создать в приложении Outlook новый контакт - объект класса ContactItem, - я использую метод CreateItem, аргументом которого являются встроенные константы, каждая из которых определяет тип создаваемого элемента. У вновь созданного контакта большое число свойств и методов и, конечно же, есть поля (свойства), соответствующие полям записи Person, так что мне осталось только передать значения полей из записи объекту контакт. Чтобы новый контакт был добавлен в папку Contacts приложения Outlook, как я уже говорил, достаточно было выполнить метод Save.

    Рассмотрим теперь работу еще с одним объектом Outlook класса AppointmentItem. Этот объект связан с календарем - папкой Calendar. При планировании рабочего дня возникает необходимость назначать встречи, совещания - эти и другие события делового дня могут быть одноразовыми, запланированными на определенный день и час. Но кроме таких событий могут быть события регулярные, появляющиеся с определенной цикличностью, например, раз в неделю или ежегодно. Каждому назначению встречи, каждому регулярному событию соответствует объект AppointmentItem, с которым можно работать программно.

    Когда создается новый контакт в папке Contacts, то автоматически создается и объект AppointmentItem, задающий такое ежегодное событие, связанное с контактом, как день рождения контакта. Это событие может быть включено или выключено. Поэтому для того, чтобы для каждого добавляемого контакта включалось соответствующее событие с предварительным уведомлением о предстоящем дне рождения контакта, необходимо найти автоматически созданный объект AppointmentItem и установить нужным образом значения его свойств. Эта задача и решается во второй части процедуры WriteToContact. Чтобы найти нужный нам объект AppointmentItem, являющийся элементом папки Calendar, предварительно необходимо добраться до самой папки. Я уже говорил, что для этих целей используется объект olNameSpace, который, если Вы помните, был введен среди глобальных объектов наряду с объектом myOl, описывающим приложение Outlook. Получив папку Calendar, я в ее коллекции элементов Items, выбираю последний элемент, поскольку понимаю, что это и есть нужный мне объект AppointmentItem, только что созданный при сохранении нового контакта. Конечно, можно считать, что мне повезло, что я таким косвенным образом знаю индекс нужного мне элемента в коллекции. Этот прием не всегда может быть использован. Так что всегда остается вопрос, как установить индекс или имя элемента в коллекции items папки Calendar и других папок, но рассмотрение этой более общей ситуации оставим на будущее.

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

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

    Преобразование данных справочника

    увеличить изображение
    Рис. 2.5.  Папка Контакты с добавленными контактами

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

    Преобразование данных справочника

    Рис. 2.6.  Окно свойств добавленного контакта

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

    Преобразование данных справочника

    Рис. 2.7.  Окно уведомления о дне рождения контакта

    Давайте взглянем еще, как выглядит запись о дне рождения контакта, добавляемая в соответствующий день календаря, открываемого папкой Calendar приложения Outlook:

    Преобразование данных справочника

    увеличить изображение
    Рис. 2.8.  Окно календаря Outlook с показом события, связанного с днем рождения контакта

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

    Преобразование данных справочника

    увеличить изображение
    Рис. 2.9.  Предупреждающие сообщения о предстоящем событии.

    На этом я завершу описание примера, в котором рассмотрено взаимодействие двух приложений - Word и Outlook. На этом я закончу и данную лекцию.

    Преобразование данных справочника
    Преобразование данных справочника
    Преобразование данных справочника
    © 2003-2007 INTUIT.ru. Все права защищены.

    Текстовый буфер задается обычной строкой

    ' Текстовый буфер задается обычной строкой Public TextBuffer As String
    Public Sub CopyText() 'Этот макрос копирует выделенный текст в буфер TextBuffer = Selection.Text End Sub
    Public Sub PasteText() 'Этот макрос выполняет операцию, обратную копированию 'Текст из буфера вставляется в точку, заданную курсором Selection.Text = TextBuffer End Sub
    Листинг 2.1.
    Закрыть окно

    Dim RowIndex As Integer Dim

    Private Sub CommandButton2_Click() DelElem End Sub
    Public Sub DelElem() 'Удаляет выбранный элемент буфера
    Dim RowIndex As Integer Dim Sel As Boolean Sel = False With BufferForm.ListBox1 For RowIndex = 0 To .ListCount - 1 If .Selected(RowIndex) Then Sel = True MultBuffer.Remove (RowIndex + 1) .RemoveItem RowIndex Exit For End If RowIndex = RowIndex + 1 Next RowIndex If Not Sel Then MsgBox ("Для удаления выберете элемент из списка!") End If End With End Sub
    Листинг 2.10.
    Закрыть окно

    Удаляет элементы из списка For

    Private Sub CommandButton3_Click() ClearAll End Sub
    Public Sub ClearAll() 'Удаляет элементы из буфера (коллекции) Dim i As Integer For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i ' Удаляет элементы из списка For i = 1 To BufferForm.ListBox1.ListCount BufferForm.ListBox1.RemoveItem (0) Next i BufferForm.Hide End Sub
    Листинг 2.11.
    Закрыть окно

    PanelName As String) As Boolean

    Public Function ExistCommandBar( PanelName As String) As Boolean 'Возвращает True, если в коллекции CommandBars 'существует панель с именем PanelName Dim bar As CommandBar, Exist As Boolean Exist = False For Each bar In CommandBars If bar.name = PanelName Or bar.NameLocal = PanelName Then Exist = True Exit For End If Next bar ExistCommandBar = Exist End Function
    Public Sub AddPanel(PanelName As String) 'Добавляет и делает видимой панель с именем Panelname 'в коллекцию Commandbars 'Панель расположена вверху документа, 'не заменяет главное меню и не является временной If Not ExistCommandBar(PanelName) Then Call CommandBars.Add(name:=PanelName, Position:=msoBarTop, _ MenuBar:=False, Temporary:=False) End If CommandBars(PanelName).Enabled = True CommandBars(PanelName).Visible = True
    End Sub
    Public Sub CreateComboPanel() 'Создание панели с элементами класса CommandBarCombobox 'Создаем панель Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox AddPanel ("MultBufferPanel") Set Panel = CommandBars("MultBufferPanel") 'Добавляем на панель Combo кнопку типа DropDown - выпадающий список Set Ctrl = AddCustomCombo(Panel, "DropdownItem", msoControlDropdown) 'Указываем обработчик события при выборе элемента списка Ctrl.OnAction = "DropdownReaction" End Sub Public Function ExistControl(Panel As CommandBar, _ Capt As String) As Boolean 'Возвращает True, если в коллекции Controls ' панели с именем Panel существует элемент с заголовком capt Dim Ctrl As CommandBarControl, Exist As Boolean Exist = False For Each Ctrl In Panel.Controls If Ctrl.Caption = Capt Then Exist = True Exit For End If Next Ctrl ExistControl = Exist End Function
    Public Function AddCustomCombo(Panel As CommandBar, _ name As String, tip As Variant) As CommandBarComboBox 'Добавляет на панель элемент, тип которого задан параметром tip 'возвращая объект CommandBarComboBox в качестве результата Dim Ctrl As CommandBarComboBox If Not ExistControl(Panel, name) Then Set Ctrl = Panel.Controls.Add(Type:=tip) Ctrl.Caption = name End If Set AddCustomCombo = Panel.Controls(name) End Function
    Листинг 2.12.
    Закрыть окно

    Этот макрос копирует выделенный объект

    Public Sub Copy1Mult() ' Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range 'Одновременно создается список элемента ComboBox 'на панели MultBufferPanel Dim Txt As String Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox If Selection.Range.Characters.Count = 1 Then NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Selection.Range.Text, 40) End If Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") 'Добавление элемента списка Ctrl.AddItem Txt End Sub
    Листинг 2.13.
    Закрыть окно

    DropDown ComboBox Dim Ctrl As

    Public Sub DropdownReaction() 'Обработчик кнопки меню - DropDown ComboBox Dim Ctrl As CommandBarComboBox Set Ctrl = CommandBars.ActionControl
    Set Elem = MultBuffer(Ctrl.ListIndex) If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If End Sub
    Листинг 2.14.
    Закрыть окно

    и DropDown списка на панели

    Public Sub DelAll() 'Удаляет элементы из буфера и DropDown списка на панели Dim i As Integer Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox 'Удаляет элементы из буфера (коллекции) For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i 'Удаление из списка Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") For i = 1 To Ctrl.ListCount Ctrl.RemoveItem (1) Next i End Sub
    Листинг 2.15.
    Закрыть окно

    Вставка всех элементов из буфера

    Public Sub InsertAll() ' Вставка всех элементов из буфера в точку, 'заданную курсором. Shape-элементы вставляются, как обычно, 'не привязанные к фиксированной позиции. For Each Elem In MultBuffer If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If Next Elem End Sub
    Листинг 2.16.
    Закрыть окно

    For Each Sym In Text

    TextResult = "" For Each Sym In Text Index = FindIndex(Sym, Source) TextResult = TextResult & Dest(Index) Next
    Листинг 2.17.
    Закрыть окно

    Dim Sym As String, Sym1

    Public Sub FromEToR() 'Translation of Symbols: England --> Russian Const ALU = "ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ" Const AL = "фисвуапршолдьтщзйкыегмцчня"
    Dim Sym As String, Sym1 As Range Dim Index As Byte Dim Result As String Dim Pravka As Boolean Dim Pravka1 As Boolean Pravka = False Pravka1 = False Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 'Исправление ошибочной автокорректировки If Pravka And (Sym <> " ") Then Sym = LCase(Sym): Pravka = False Select Case Sym Case "A" To "Z" 'английская буква верхнего регистра Index = Asc(Sym) - Asc("A") + 1 Sym = Mid(ALU, Index, 1) Case "a" To "z" 'английская буква нижнего регистра Index = Asc(Sym) - Asc("a") + 1 Sym = Mid(AL, Index, 1) 'Символы, переходящие в символы Case "?": Sym = "," Case "/": Sym = "." Case "^": Sym = ":" Case "$": Sym = ";" Case "&": Sym = "?" Case "@": Sym = """" Case "#": Sym = "№" 'Символы, переходящие в буквы Case ",": Sym = "б" Case "<": Sym = "Б" Case ".": Sym = "ю" Case ">": Sym = "Ю" Case ";": Sym = "ж" Case ":": Sym = "Ж" Case "'": Sym = "э" Case """": Sym = "Э" Case "[": Sym = "х" Case "]": Sym = "ъ" Case "{": Sym = "Х" Case "}": Sym = "Ъ" Case "`": Sym = "ё" Case "~": Sym = "Ё" 'Другие виды кавычек Case Chr(145): Sym = "э" Case Chr(146): Sym = "э" Case Chr(147): Sym = "Э" Case Chr(148): Sym = "Э" Case Chr(171): Sym = "Э" Case Chr(187): Sym = "Э" Case Else: 'Кодировки совпадают End Select 'Обнаружение ошибочной автокорректировки If Sym = "," Then Pravka = True If Pravka1 And (Sym = " ") Then Pravka = True Else: Pravka1 = False End If If Sym = "ю" Then Pravka1 = True 'Формирование результата Result = Result + Sym Next Selection.LanguageID = wdRussian Selection.TypeText Result End Sub
    Листинг 2.18.
    Закрыть окно

    Dim Sym As String, Sym1

    Public Sub FromRToE() 'Translation of Symbols: Russian --> England Const ALU = "FZ" Const AL = "f,dult;pbqrkvyjghcnea[wxio]sm'.z"
    Dim Sym As String, Sym1 As Range Dim Index As Byte Dim Result As String Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 Select Case Sym Case "А" To "Я" 'русская буква верхнего регистра Index = Asc(Sym) - Asc("А") + 1 Sym = Mid(ALU, Index, 1) Case "а" To "я" 'русская буква нижнего регистра Index = Asc(Sym) - Asc("а") + 1 Sym = Mid(AL, Index, 1) 'Символы, переходящие в символы Case "?": Sym = "&" Case ".": Sym = "/" Case ",": Sym = "?" Case ";": Sym = "$" Case "№": Sym = "#" Case ":": Sym = "^" Case """": Sym = "@" Case Chr(147): Sym = "@" Case Chr(148): Sym = "@" Case Chr(171): Sym = "@" Case Chr(187): Sym = "@" Case "ё": Sym = "`" Case "Ё": Sym = "~" Case Else: 'Кодировки совпадают End Select 'Устранение результатов автоматической правки текста Result = Result + Sym Next Selection.LanguageID = wdEnglishUS Selection.TypeText Result End Sub
    Листинг 2.19.
    Закрыть окно

    и шрифт Public Type TextAndFont

    'Буфер, сохраняющий текст и шрифт Public Type TextAndFont BufText As String BufFont As Font End Type
    Public TaFBuffer As TextAndFont
    Листинг 2.2.
    Закрыть окно

    To 32) As String

    Public Sub FromRuToLat() 'Translation of Symbols: Russian --> Latin Dim ALU( 1 To 32) As String ALU(1) = "A": ALU(2) = "B": ALU(3) = "V": ALU(4) = "G" ALU(5) = "D": ALU(6) = "E": ALU(7) = "J": ALU(8) = "Z" ALU(9) = "I": ALU(10) = "I": ALU(11) = "K": ALU(12) = "L" ALU(13) = "M": ALU(14) = "N": ALU(15) = "O": ALU(16) = "P" ALU(17) = "R": ALU(18) = "S": ALU(19) = "T": ALU(20) = "U" ALU(21) = "F": ALU(22) = "H": ALU(23) = "C": ALU(24) = "Ch" ALU(25) = "Sh": ALU(26) = "Sch": ALU(27) = "'": ALU(28) = "Y" ALU(29) = "'": ALU(30) = "E": ALU(31) = "Yu": ALU(32) = "Ya"
    Dim Sym As String, Sym1 As Range Dim Index As Byte Dim S As String Dim Result As String Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 Sym = UCase(Sym) Select Case Sym Case "А" To "Я" ' буква верхнего регистра Index = Asc(Sym) - Asc("А") + 1 S = ALU(Index) If Sym <> Sym1 Then S = LCase(S) 'Символ в нижнем регистре Sym = S Case "Ё" S = "E" If Sym <> Sym1 Then S = LCase(S) 'Символ в нижнем регистре Sym = S Case Else 'Кодировки совпадают Sym = Sym1 End Select Result = Result + Sym Next Selection.TypeText Result End Sub
    Листинг 2.20.
    Закрыть окно

    Эта процедура преобразует выделенный программный

    Sub RepNew() ' Эта процедура преобразует выделенный программный текст 'Заменяя пробелы табуляцией и конец абзаца мягким концом строки Dim MyRange As Range, TxtRange As String Dim StrFind As String, strReplace As String Debug.Print Val(vbCrLf), Val(vbLf) Set MyRange = Selection.Range TxtRange = MyRange.Text 'Замена концов абзаца StrFind = vbCr 'Chr(13) - Конец абзаца strReplace = vbVerticalTab 'Chr(11) - Разрыв строки TxtRange = Replace(TxtRange, StrFind, strReplace) 'Замена пробелов табуляцией StrFind = " " '4 пробела strReplace = vbTab 'символ табуляции TxtRange = Replace(TxtRange, StrFind, strReplace)
    StrFind = " " '3 пробела strReplace = vbTab 'символ табуляции TxtRange = Replace(TxtRange, StrFind, strReplace)
    StrFind = " " '2 пробела strReplace = vbTab 'символ табуляции TxtRange = Replace(TxtRange, StrFind, strReplace)
    MyRange.Text = TxtRange 'Замена стиля на стиль "Listing", если он встроен Dim MyStyle As Style For Each MyStyle In ActiveDocument.Styles If MyStyle.NameLocal = "Listing" Then MyRange.Style = "Listing" Next MyStyle
    End Sub
    Листинг 2.21.
    Закрыть окно

    Dim Sym As String, Sym1

    Public Sub CodeDA() 'Кодировка Rus -> Rus Const ALU = "бвчздецъйклмнопртуфхжигюыэящшьас" Const AL = "БВЧЗДЕЦЪЙКЛМНОПРТУФХЖИГЮЫЭЯЩШЬАС" Dim Sym As String, Sym1 As Variant Dim Index As Integer Dim Result As String Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 Select Case Sym Case "А" To "Я" 'русская буква верхнего регистра Index = Asc(Sym) - Asc("А") + 1 Sym = Mid(ALU, Index, 1) Case "а" To "я" 'английская буква нижнего регистра Index = Asc(Sym) - Asc("а") + 1 Sym = Mid(AL, Index, 1) End Select Result = Result + Sym Next Sym1 Selection.LanguageID = wdRussian Selection.TypeText Result
    End Sub
    Листинг 2.22.
    Закрыть окно

    Dim Sym As String, Sym1

    Public Sub CodeDA() Const ALU = "бвчздецъйклмнопртуфхжигюыэящшьас" Const AL = "БВЧЗДЕЦЪЙКЛМНОПРТУФХЖИГЮЫЭЯЩШЬАС" Dim Sym As String, Sym1 As Variant Dim Index As Integer Dim Result As String 'Переменные, задающие начальное время участка процедуры Dim Start1 As Single, Start2 As Single, Start3 As Single, Start4 As Single Dim Start5 As Single, Start6 As Single, Start7 As Single, Start8 As Single 'Переменные, накапливающие время выполнения участка программы Dim TALL As Single, TSelect As Single, TIndex1 As Single, TIndex2 As Single Dim TSym1 As Single, TSym2 As Single, TForEach As Single
    Dim i As Integer Start1 = Timer Result = "" Start2 = Timer 'Два способа организации цикла! 'For Each Sym1 In Selection.Characters For i = 1 To Selection.Characters.Count Sym1 = Selection.Characters(i)
    Start3 = Timer Sym = Sym1 'Два способа организации разбора случаев 'Select Case Sym 'Case "А" To "Я" 'русская буква верхнего регистра If Sym >= "А" And Sym <= "Я" Then
    Start5 = Timer Index = Asc(Sym) - Asc("А") + 1 TIndex1 = TIndex1 + Timer - Start5 Start6 = Timer Sym = Mid(ALU, Index, 1) TSym1 = TSym1 + Timer - Start6 'End If 'Case "а" To "я" 'русская буква нижнего регистра ElseIf Sym >= "а" And Sym <= "я" Then Start7 = Timer Index = Asc(Sym) - Asc("а") + 1 TIndex2 = TIndex2 + Timer - Start7 Start8 = Timer Sym = Mid(AL, Index, 1) TSym2 = TSym2 + Timer - Start8 'End Select End If TSelect = TSelect + Timer - Start3 Result = Result + Sym Next TForEach = TForEach + Timer - Start2 Selection.LanguageID = wdRussian Selection.TypeText Result TALL = TALL + Timer - Start1 Debug.Print "TAll = ", TALL Debug.Print "TForEach = ", TForEach Debug.Print "TSelect = ", TSelect Debug.Print "TIndex1 = ", TIndex1 Debug.Print "TIndex2 = ", TIndex2 Debug.Print "TSym1 = ", TSym1 Debug.Print "TSym2 = ", TSym2
    End Sub
    Листинг 2.23.
    Закрыть окно

    End Select на конструкцию: If

    Select Case Sym Case "А" To "Я" <операторы1> Case "а" To "я" <операторы2> End Select на конструкцию: If Sym >= "А" And Sym <= "Я" Then <операторы1> ElseIf Sym >= "а" And Sym <= "я" Then <операторы1> End If
    Листинг 2.24.
    Закрыть окно

    For Each Sym1 In

    For Each Sym1 In Selection.Characters
    Листинг 2.25.
    Закрыть окно

    For i = 1 To Selection.Characters

    For i = 1 To Selection.Characters.Count Sym1 = Selection.Characters(i)
    Листинг 2.26.
    Закрыть окно

    и его компоненты Public myOl

    Option Explicit 'Объект Outlook и его компоненты Public myOl As Outlook.Application, olNameSpace As NameSpace 'Коллекция избранных личностей Public CollectionOfPersons As New Collection 'Коллекция номеров абзацев, задающих начало записей Public Numbers As New Collection Public Con As New Collection 'Определение типа - записи, характеризующей личность Public Type Person FirstName As String LastName As String MiddleName As String Post As String DOB As Date Address As String Tel As String Email As String Fax As String Other As String End Type
    Листинг 2.27.
    Закрыть окно

    в базу данных Контакты приложения

    Public Sub WTOOL() 'Процедура преобразует справочник персоналий ' в базу данных Контакты приложения Outlook
    'Формирование списка персоналий и выбор пользователя Call FormList 'Создание записей для избранных Call SelectPerson End Sub
    Листинг 2.28.
    Закрыть окно

    Эта процедура формирует список личностей

    Public Sub FormList() ' Эта процедура формирует список личностей Dim par As Paragraph, parStyle As String Dim i As Integer, n As Integer With ActiveDocument parStyle = .Paragraphs(1).Style n = ActiveDocument.Paragraphs.Count i = 1 For Each par In .Paragraphs If par.Style = parStyle Then 'Добавить элемент в список и номер абзаца в коллекцию frmPersons.lstPersons.AddItem par.Range.Text Numbers.Add i End If i = i + 1 Next par End With frmPersons.Show End Sub
    Листинг 2.29.
    Закрыть окно

    Этот макрос копирует выделенный текст

    Public Sub CopyTextAndFont() ' Этот макрос копирует выделенный текст и шрифт в буфер Set TaFBuffer.BufFont = Selection.Font TaFBuffer.BufText = Selection.Text End Sub
    Public Sub PasteTextAndFont() 'Этот макрос выполняет операцию, обратную копированию
    'К сожалению, такое присваивание свойства Font 'для объекта Selection не проходит?! 'Selection.Font = TaFBuffer.BufFont 'Но можно присвоить свойства объекту Font Selection.Font.Name = TaFBuffer.BufFont.Name Selection.Font.Bold = TaFBuffer.BufFont.Bold Selection.Font.Italic = TaFBuffer.BufFont.Italic Selection.Font.Size = TaFBuffer.BufFont.Size 'Текст из буфера с указанными параметрами шрифта 'вставляется в точку, заданную курсором. Selection.Text = TaFBuffer.BufText End Sub
    Листинг 2.3.
    Закрыть окно

    Dim intLoop As Integer, intSelect

    Private Sub cmdSelectPerson_Click() Dim intLoop As Integer, intSelect As Integer Dim strSelect As String Dim ВыборСделан As Boolean Dim Num As Integer ВыборСделан = False intLoop = 0 intSelect = 0 'Поиск выделенных элементов Do If frmPersons.lstPersons.Selected(intLoop) Then 'Найден очередной элемент strSelect = frmPersons.lstPersons.List(intLoop) Num = intLoop + 1 ВыборСделан = True intSelect = intSelect + 1 CollectionOfPersons.Add strSelect Con.Add Num End If intLoop = intLoop + 1 Loop Until intLoop = frmPersons.lstPersons.ListCount If ВыборСделан Then Unload Me Set myOl = CreateObject("Outlook.Application") Else MsgBox ("Выбор не сделан") End If 'Печать коллекции 'For Each pers In CollectionOfPersons ' Debug.Print pers 'Next pers End Sub
    Листинг 2.30.
    Закрыть окно

    Выделение записи Dim par As

    Public Sub SelectPerson()
    ' Выделение записи Dim par As Paragraph, CountPar As Integer Dim ibeg As Integer, ifin As Integer, nPerson As Integer Dim PersonRange As Range Dim i As Integer, Num As Variant With ActiveDocument Set PersonRange = .Paragraphs(1).Range i = 0 'Цикл по записям, отобранных пользователем For Each Num In Con i = i + 1 'Выделение области документа, занятой записью 'Номер абзаца, начинающего запись ibeg = Numbers(Num) 'Номер абзаца, заканчивающего запись ifin = Numbers(Con(i) + 1) - 1 PersonRange.Start = .Paragraphs(ibeg).Range.Start PersonRange.End = .Paragraphs(ifin).Range.End 'Выделение записи PersonRange.Select 'Обработать запись - объект Selection Call WorkWithSelected Next End With myOl.Quit
    End Sub
    Листинг 2.31.
    Закрыть окно

    и отмеченной записью Dim pers

    Public Sub WorkWithSelected() 'Обработка с выбранной и отмеченной записью Dim pers As Person, pars As Paragraphs, par As Paragraph Dim i As Integer, n As Integer Dim myR As Range Dim FirstWord As String Set pars = Selection.Paragraphs With pers 'Обработка первого абзаца - фамилии Set par = pars(1) Set myR = par.Range .FirstName = myR.Words(2).Text .MiddleName = myR.Words(3).Text .LastName = myR.Words(1).Text 'Обработка должности - следующего непустого абзаца Set par = pars(2) If par.Range.Words.Count = 1 Then Set par = pars(3) Set myR = par.Range n = myR.Words.Count myR.End = par.Range.Words(n - 1).End .Post = myR.Text 'Обработка оставшихся абзацев For Each par In pars Set myR = par.Range n = myR.Words.Count FirstWord = myR.Words(1).Text Select Case FirstWord Case "Родился ", "Родилась " .DOB = SelectDate(par.Range) Case "Тел" myR.Start = par.Range.Words(3).Start myR.End = par.Range.Words(n - 1).End .Tel = myR.Text Case "Факс" myR.Start = par.Range.Words(3).Start myR.End = par.Range.Words(n - 1).End .Fax = myR.Text Case "Адрес" myR.Start = par.Range.Words(3).Start myR.End = par.Range.Words(n - 1).End .Address = myR.Text Case "e" myR.Start = par.Range.Words(5).Start myR.End = par.Range.Words(n - 1).End .Email = myR.Text Case Else .Other = .Other + myR.Text End Select Next par If Not IsDate(.DOB) Then .DOB = "1 января " 'Debug.Print Selection.Range.Text 'Debug.Print .FIO, .Post, .Address, .DOB, .Tel, .Fax, .Email, .Other End With 'Запись создана - теперь создается контакт в Outlook Call WriteToContact(pers) End Sub
    Листинг 2.32.
    Закрыть окно

    ran As Range) As String

    Public Function SelectDate( ran As Range) As String Dim Dat As String With ran If .Words(3) = "февраля " Then .Words(3) = "фев " Dat = .Words(2) & .Words(3) & .Words(4) If IsDate(Dat) Then SelectDate = Dat Else Dat = .Words(2) & .Words(3) If IsDate(Dat) Then SelectDate = Dat Else Dat = "1 января " & .Words(3) If IsDate(Dat) Then SelectDate = Dat Else Dat = "1 января " End If End If End If
    End With End Function
    Листинг 2.33.
    Закрыть окно

    False ?IsDate

    ?IsDate(" 13 февраля 1961") False ?IsDate("13 фев. 1961") True
    Листинг 2.34.
    Закрыть окно

    о нем Dim newContact As

    Public Sub WriteToContact(pers As Person) 'Создание нового контакта в Outlook и запись данных о нем Dim newContact As ContactItem Set newContact = myOl.CreateItem(olContactItem) 'newContact.Display With newContact .BirthDay = pers.DOB .FirstName = pers.FirstName .MiddleName = pers.MiddleName .LastName = pers.LastName .BusinessFaxNumber = pers.Fax .BusinessTelephoneNumber = pers.Tel .BusinessAddress = pers.Address .Email1Address = pers.Email .JobTitle = pers.Post .Save End With ' newContact.Save 'Включить предупреждение о дне рождения Dim myFolder As MAPIFolder Dim newAppointment As Object Set olNameSpace = myOl.GetNamespace("MAPI") Set myFolder = olNameSpace.GetDefaultFolder(olFolderCalendar) Set newAppointment = myFolder.Items(myFolder.Items.Count) With newAppointment .ReminderSet = True .ReminderMinutesBeforeStart = 1440 .Save End With
    End Sub
    Листинг 2.35.
    Закрыть окно

    позволяющий сохранять объект Public ObjectBuffer

    'Буфер, позволяющий сохранять объект Public ObjectBuffer As Range
    Public Sub CopyObject() 'Этот макрос копирует выделенный объект в буфер Set ObjectBuffer = Selection.Range End Sub
    Public Sub PasteObject() 'Этот макрос выполняет операцию, обратную копированию. 'Объект из буфера вставляется в точку, заданную курсором. 'Поскольку объект может быть сложным и содержать, например, 'рисунки, то используется техника копирования через стандартный буфер! ObjectBuffer.Copy Selection.PasteSpecial
    End Sub
    Листинг 2.4.
    Закрыть окно

    Множественный буфер можно задать массивом

    Option Explicit
    ' Множественный буфер можно задать массивом или динамической структурой 'Я предпочитаю использовать динамическую структуру Public MultBuffer As New Collection Public Elem As Range Public NumElem As Integer
    Листинг 2.5.
    Закрыть окно

    Этот макрос копирует выделенный объект

    Public Sub CopyMult() ' Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range End Sub
    Листинг 2.6.
    Закрыть окно

    Dim Txt As String NumElem

    Private Sub UserForm_Activate() Dim Txt As String NumElem = 0 For Each Elem In MultBuffer 'Анализ типа элемента буфера и создание элемента списка If Elem.Characters.Count = 1 Then 'Это объект Shape, InlineShape, 'специальный или однобуквенный символ! NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Elem.Text, 40) End If 'Добавление элемента в список формы ListBox1.AddItem Txt Next Elem
    End Sub
    Листинг 2.7.
    Закрыть окно

    с элементами буфера

    Public Sub PasteMult() 'Показывает форму с элементами буфера BufferForm.Show End Sub
    Листинг 2.8.
    Закрыть окно

    в точку, заданную курсором Dim

    Private Sub CommandButton1_Click() InsertElem End Sub
    Public Sub InsertElem() 'Вставляет выбранный элемент буфера ' в точку, заданную курсором Dim RowIndex As Integer Dim Sel As Boolean Sel = False With BufferForm.ListBox1 For RowIndex = 0 To .ListCount - 1 If .Selected(RowIndex) Then Sel = True Set Elem = MultBuffer(RowIndex + 1) If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If Exit For End If Next RowIndex If Not Sel Then MsgBox ("Для вставки выберете элемент из списка!") End If End With End Sub
    Листинг 2.9.
    Закрыть окно

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

    Чтобы эффективно работать с текстовыми документами, необходимо хорошо знать объекты Word, описанные в предыдущей лекции. Без знания основных коллекций, задающих структуру документа, - абзацев, предложений, слов, символов, без знания объектов Range и Selection, не обойтись. С другой стороны необходимо владение встроенными функциями VBA для работы со строковыми переменными. Умение работы с объектами Word и функциями VBA позволяет достаточно просто решать самые разнообразные задачи, возникающие в ходе работы с текстовыми документами. В предыдущей лекции, где я рассматривал объекты Word, я приводил большое число примеров, иллюстрирующих работу с теми или иными объектами. Но там рассмотрение шло от "объектов", теперь же я хочу идти от "задач", которые могут возникать при работе с текстовыми документами. Давайте перейдем к рассмотрению некоторых примеров.

    Программная работа с документами Word

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

    Работа с текстовыми базами данных

    Теперь, когда есть Access и FoxPro, Oracle и SQL Server говорить о текстовых базах данных просто неприлично. Тем не менее, возьму на себя смелость заявить, что и текстовые базы данных будут жить еще очень долго, в том числе и в электронной форме. Кто из нас не пользуется различными справочниками и словарями? Текстовая форма представления таких документов привычна для человека. Поэтому я решил в качестве более сложного примера работы с текстовым документом привести задачу работы с некоторым справочником.
    Замечу, что хотя Word и не предназначен для решения подобных задач, в нем есть достаточно средств, используя которые программист может организовать выполнение всех функций, необходимых при работе с тем или иным справочником, словарем, той или иной текстовой базой данных, содержащей, например, информацию о сотрудниках предприятия или магазинах Москвы. О многих способах и объектах, задающих структуризацию текста, я уже говорил. Напомню, всегда есть возможность работы с объектами, задающими коллекции разделов, абзацев, предложений и слов. Добавлю к этому, что структуризация текста задается и за счет использования различных стилей, что широко может использоваться в работе с текстовыми базами данных. Нельзя не упомянуть и коллекцию закладок, специально предназначенных для быстрого поиска нужного фрагмента в текстовом документе. Есть в Word и другие средства, предназначенные для поиска, выделения и вставки фрагментов текста в документ. А что еще нужно при работе со справочником или словарем?

    Реализация множественного буфера, основанная на форме

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

    Рис. 2.2.  Форма, отображающая элементы множественного буфера
    Три командные кнопки, помещенные в форму, - "Вставить элемент", "Удалить элемент" и "Удалить все" - выполняют основные операции над элементами буфера. Отмечу одно важное свойство, установленное при проектировании формы BufferForm. Я сделал эту форму немодальной, установив булево свойство ShowModal как False. Это позволяет держать форму открытой при работе с документом и при необходимости вставлять нужные элементы из открытого списка.
    Прежде, чем продолжить разговор о работе с этой формой, давайте займемся операцией добавления элементов в наш буфер. Вот как реализуется копирование выделенных объектов документа Word в наш множественный буфер. Приведу текст соответствующего макроса:
    Public Sub CopyMult() 'Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range End Sub
    Листинг 2.6.
    (html, txt)
    Как видите, в макросе копирования элементов в буфер для добавления элементов в коллекцию, используется метод Add, вся задача выполняется одним оператором и не требует особых пояснений. Заметьте, в коллекцию полностью добавляется выделенный объект, а не его текст или другие компоненты.
    Конечно, в этот же момент можно было бы создавать и соответствующий элемент списка нашей формы. Но разумнее это делать в другом месте - в обработчике события, возникающего при активизации формы. Действительно, если представить, что форма уже открыта и расположена на экране, то программное добавление нового элемента в ее список не будет отображаться, пока форма не будет перерисована. Раз так, то целесообразнее каждый раз при активизации формы (появлении ее на экране) создавать список на основе текущего состояния множественного буфера - коллекции MultBuffer. Вот как выглядит для формы написанный мной обработчик события Activate, решающий задачу создания элементов списка формы. Естественно, что список создается на основе анализа состояния буфера:

    Инструментальные кнопки класса ComboBox могут быть одного из трех типов, задавая окно редактирования, выпадающий список и список с окном редактирования. Кнопкой типа DropDown - выпадающий список можно воспользоваться для реализации множественного буфера. Вместо того, чтобы показывать список элементов буфера в отдельной форме, во многих случаях предпочтительнее иметь в меню кнопку типа DropDown, при работе с которой раскрывается список, отображающий элементы буфера.
    Заметьте, этот прием может быть полезен не только в данном конкретном примере, но и во многих случаях, когда приходится предъявлять пользователю некоторый список для того, чтобы он сделал в нем свой выбор. Кнопка меню, конечно же, компактнее, чем отдельная открывающаяся форма. Именно по этой причине такой способ работы с множественным буфером может быть предпочтительнее.
    Хочу обратить Ваше внимание, что стандартная реализация фактически сочетает оба эти способа. В стандартной реализации буфер обычно появляется в виде плавающей формы, но когда окно формы, являющееся стыковочным окном, "причаливает" к одному из краев экрана, то форма превращается в обычную инструментальную панель, и элементы буфера отображаются уже в списке кнопки типа Dropdown. Лично я предпочитаю при работе со стандартным буфером представлять его в виде инструментальной панели, но это, конечно, дело вкуса. Что же касается собственной реализации, то я предпочел разделить оба эти способа.
    Перейдем теперь к рассмотрению деталей реализации. Замечу, что представление самого буфера в виде коллекции остается неизменным в обеих реализациях. Теперь только вместо формы появится инструментальная панель с соответствующими кнопками. Наряду с кнопкой типа DropDown, которая будет реализовывать операцию вставки элемента буфера, на инструментальной панели будут расположены и обычные командные кнопки, реализующие другие операции над буфером. Кнопку DropDown необходимо создать программно. Конечно, программно можно создать и саму панель, и все кнопки, расположенные на ней. Но я предпочел сочетать работу руками и программную работу. Вот как выглядит созданная инструментальная панель MultBufferPanel:
    Реализация множественного буфера, основанная на форме

    Рис. 2.3.  Панель MultBufferPanel с кнопкой типа DropDown
    Две кнопки в первой группе RangeShape и PasteMult позволяют работать с множественным буфером, основанным на первой его реализации. Вторая группа кнопок предназначена для работы с буфером, основанным на втором способе реализации. Кнопка Copy1Mult позволяет копировать выделенный объект в документе Word в буфер, основанный на рассматриваемой нами DropDown реализации. Вторая кнопка, созданная программно, имеет тип DropDown и используется для выбора и вставки нужного элемента буфера в позицию, заданную курсором. Кнопка DelAll предназначена для чистки буфера, а кнопка InsertAll для вставки всех элементов буфера в позицию, заданную курсором. Заметьте, иногда такая операция бывает полезной. Также как и в стандартной реализации, отсутствует операция, позволяющая удалять единственный выбранный элемент буфера. Причина этого понятна, - выбор элемента в списке кнопки однозначно определяет и операцию, выполняемую над этим элементом. Такой операцией является операция вставки выбранного элемента. Для удаления элемента потребовалось бы иметь еще одну кнопку типа DropDown.
    Все кнопки на панели MultBufferPanel можно создать руками, что я и делал. Единственным исключением является кнопка DropDown, которую нужно создавать программно. Рассмотрим, как это делается. Приведу сейчас тексты всех используемых мной процедур:


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

    Рис. 2.2.  Форма, отображающая элементы множественного буфера
    Три командные кнопки, помещенные в форму, - "Вставить элемент", "Удалить элемент" и "Удалить все" - выполняют основные операции над элементами буфера. Отмечу одно важное свойство, установленное при проектировании формы BufferForm. Я сделал эту форму немодальной, установив булево свойство ShowModal как False. Это позволяет держать форму открытой при работе с документом и при необходимости вставлять нужные элементы из открытого списка.
    Прежде, чем продолжить разговор о работе с этой формой, давайте займемся операцией добавления элементов в наш буфер. Вот как реализуется копирование выделенных объектов документа Word в наш множественный буфер. Приведу текст соответствующего макроса:
    Public Sub CopyMult() 'Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range End Sub
    Листинг 2.6.
    Как видите, в макросе копирования элементов в буфер для добавления элементов в коллекцию, используется метод Add, вся задача выполняется одним оператором и не требует особых пояснений. Заметьте, в коллекцию полностью добавляется выделенный объект, а не его текст или другие компоненты.
    Конечно, в этот же момент можно было бы создавать и соответствующий элемент списка нашей формы. Но разумнее это делать в другом месте - в обработчике события, возникающего при активизации формы. Действительно, если представить, что форма уже открыта и расположена на экране, то программное добавление нового элемента в ее список не будет отображаться, пока форма не будет перерисована. Раз так, то целесообразнее каждый раз при активизации формы (появлении ее на экране) создавать список на основе текущего состояния множественного буфера - коллекции MultBuffer. Вот как выглядит для формы написанный мной обработчик события Activate, решающий задачу создания элементов списка формы. Естественно, что список создается на основе анализа состояния буфера:


    Инструментальные кнопки класса ComboBox могут быть одного из трех типов, задавая окно редактирования, выпадающий список и список с окном редактирования. Кнопкой типа DropDown - выпадающий список можно воспользоваться для реализации множественного буфера. Вместо того, чтобы показывать список элементов буфера в отдельной форме, во многих случаях предпочтительнее иметь в меню кнопку типа DropDown, при работе с которой раскрывается список, отображающий элементы буфера.
    Заметьте, этот прием может быть полезен не только в данном конкретном примере, но и во многих случаях, когда приходится предъявлять пользователю некоторый список для того, чтобы он сделал в нем свой выбор. Кнопка меню, конечно же, компактнее, чем отдельная открывающаяся форма. Именно по этой причине такой способ работы с множественным буфером может быть предпочтительнее.
    Хочу обратить Ваше внимание, что стандартная реализация фактически сочетает оба эти способа. В стандартной реализации буфер обычно появляется в виде плавающей формы, но когда окно формы, являющееся стыковочным окном, "причаливает" к одному из краев экрана, то форма превращается в обычную инструментальную панель, и элементы буфера отображаются уже в списке кнопки типа Dropdown. Лично я предпочитаю при работе со стандартным буфером представлять его в виде инструментальной панели, но это, конечно, дело вкуса. Что же касается собственной реализации, то я предпочел разделить оба эти способа.
    Перейдем теперь к рассмотрению деталей реализации. Замечу, что представление самого буфера в виде коллекции остается неизменным в обеих реализациях. Теперь только вместо формы появится инструментальная панель с соответствующими кнопками. Наряду с кнопкой типа DropDown, которая будет реализовывать операцию вставки элемента буфера, на инструментальной панели будут расположены и обычные командные кнопки, реализующие другие операции над буфером. Кнопку DropDown необходимо создать программно. Конечно, программно можно создать и саму панель, и все кнопки, расположенные на ней. Но я предпочел сочетать работу руками и программную работу. Вот как выглядит созданная инструментальная панель MultBufferPanel:
    Реализация множественного буфера, основанная на форме

    Рис. 2.3.  Панель MultBufferPanel с кнопкой типа DropDown
    Две кнопки в первой группе RangeShape и PasteMult позволяют работать с множественным буфером, основанным на первой его реализации. Вторая группа кнопок предназначена для работы с буфером, основанным на втором способе реализации. Кнопка Copy1Mult позволяет копировать выделенный объект в документе Word в буфер, основанный на рассматриваемой нами DropDown реализации. Вторая кнопка, созданная программно, имеет тип DropDown и используется для выбора и вставки нужного элемента буфера в позицию, заданную курсором. Кнопка DelAll предназначена для чистки буфера, а кнопка InsertAll для вставки всех элементов буфера в позицию, заданную курсором. Заметьте, иногда такая операция бывает полезной. Также как и в стандартной реализации, отсутствует операция, позволяющая удалять единственный выбранный элемент буфера. Причина этого понятна, - выбор элемента в списке кнопки однозначно определяет и операцию, выполняемую над этим элементом. Такой операцией является операция вставки выбранного элемента. Для удаления элемента потребовалось бы иметь еще одну кнопку типа DropDown.
    Все кнопки на панели MultBufferPanel можно создать руками, что я и делал. Единственным исключением является кнопка DropDown, которую нужно создавать программно. Рассмотрим, как это делается. Приведу сейчас тексты всех используемых мной процедур:


    Private Sub UserForm_Activate() Dim Txt As String NumElem = 0 For Each Elem In MultBuffer 'Анализ типа элемента буфера и создание элемента списка If Elem.Characters.Count = 1 Then 'Это объект Shape, InlineShape, 'специальный или однобуквенный символ! NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Elem.Text, 40) End If 'Добавление элемента в список формы ListBox1.AddItem Txt Next Elem

    End Sub

    Листинг 2.7.

    (html, txt)

    В цикле по объектам коллекции, составляющим буфер, анализируется их тип, и создаются текстовые элементы, представляющие элементы списка нашей формы. Заметьте, я достаточно просто расправляюсь с проблемой распознавания типа объекта, хранящегося в буфере. Для распознавания я использую следующий факт, - для всех рисованных объектов, объектов WordArt и подобных им объектов документа Word, свойство текст задается одним специальным символом. Поэтому я применяю следующее правило, - все объекты буфера, для которых свойство Characters возвращает коллекцию символов, содержащую ровно один символ, относятся к нетекстовым объектам или не могут, я полагаю, быть распознанными пользователем по этому символу. Поэтому такие объекты должны отображаться в списке словом "Объект" с соответствующим номером. Под это определение подпадают объекты Shape и InlineShape, специальные символы и, конечно, просто однобуквенные символы. Конечно, можно было бы предусмотреть более сложный анализ, но и принятое решение представляется вполне разумным и вполне удовлетворительно, как мне кажется, на практике. Некоторое сомнение может возникать в тех случаях, когда действительно копируются однобуквенные символы, но, заметьте, специальные символы целесообразно считать объектами, а простые символы вряд ли стоить копировать в множественный буфер. Далее я все-таки покажу, как можно было бы более детально проанализировать тип объекта, хранящегося в буфере.

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


    Public Sub PasteMult() 'Показывает форму с элементами буфера BufferForm.Show End Sub

    Листинг 2.8.

    (html, txt)

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

    Листинг 2.9.

    (html, txt)

    Несколько слов о том, как реализована вставка. Вначале я определяю, какой элемент списка выбран, что позволяет определить индекс элемента буфера (коллекции), который должен быть вставлен в позицию курсора. На этом этапе также приходится анализировать тип вставляемого объекта, поскольку объекты Shape не могут быть вставлены в позицию, заданную курсором. Более того, они не могут быть скопированы стандартным способом через буфер, для них приходится применять метод Duplicate, специально предназначенный для этих целей. Заметьте, в данной ситуации необходимо более корректно анализировать тип объекта, находящегося в буфере. Для распознавания того, что объект буфера принадлежит классу Shape, я вызываю метод RangeShape, возвращающий коллекцию Shapes объекта Range. Если эта коллекция не пуста, то имеем дело с объектом Shape.

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

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


    Листинг 2.10.

    (html, txt)

    Заметьте, выбранный элемент удаляется как из списка, так и из буфера

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

    Private Sub CommandButton3_Click() ClearAll End Sub

    Public Sub ClearAll() 'Удаляет элементы из буфера (коллекции) Dim i As Integer For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i 'Удаляет элементы из списка For i = 1 To BufferForm.ListBox1.ListCount BufferForm.ListBox1.RemoveItem (0) Next i BufferForm.Hide End Sub

    Листинг 2.11.

    (html, txt)

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

    on_load_lecture()

    Реализация множественного буфера, основанная на форме
    Реализация множественного буфера, основанная на форме
    Дальше "

    Реализация множественного буфера, основанная на форме
    Если Вы заметили ошибку - сообщите нам.
    Реализация множественного буфера, основанная на форме
    Страницы:

    " |

    1

    |

    2

    |

    3

    |

    4

    |

    5

    |

    6

    |

    7

    |

    вопросы | "

    |

    для печати и PDA

    Реализация множественного буфера, основанная на форме
    Реализация множественного буфера, основанная на форме
    Реализация множественного буфера, основанная на форме
    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование



    Листинг 2.12.

    (html, txt)

    Основной здесь является процедура CreateComboPanel, при запуске которой будет создана панель с именем MultBufferPanel, если она не была уже создана ранее, и на эту панель будет помещена кнопка типа Dropdown. Обратите внимание, что в этой процедуре задается обработчик события, которому я дал имя DropdownReaction, вызываемый в ответ на выбор пользователем элемента списка. Этот обработчик и будет выполнять основную операцию по вставке элемента буфера в позицию заданную курсором. Текст его приведу чуть позже, а сейчас замечу, что саму работу по добавлению данной кнопки на панель выполняет функция AddCustomCombo, которая не только создает кнопку, но и присваивает ей заголовок (Caption) "DropdownItem".

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

    Public Sub Copy1Mult() 'Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range 'Одновременно создается список элемента ComboBox 'на панели MultBufferPanel Dim Txt As String Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox If Selection.Range.Characters.Count = 1 Then NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Selection.Range.Text, 40) End If Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") 'Добавление элемента списка Ctrl.AddItem Txt End Sub

    Листинг 2.13.

    (html, txt)

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

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

    Public Sub DropdownReaction() 'Обработчик кнопки меню - DropDown ComboBox Dim Ctrl As CommandBarComboBox Set Ctrl = CommandBars.ActionControl


    Set Elem = MultBuffer(Ctrl.ListIndex) If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан ' к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If End Sub

    Листинг 2.14.

    (html, txt)

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

    Макрос DelAll выполняет чистку буфера и списка:

    Public Sub DelAll() 'Удаляет элементы из буфера и DropDown списка на панели Dim i As Integer Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox 'Удаляет элементы из буфера (коллекции) For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i 'Удаление из списка Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") For i = 1 To Ctrl.ListCount Ctrl.RemoveItem (1) Next i End Sub

    Листинг 2.15.

    (html, txt)

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

    Public Sub InsertAll() 'Вставка всех элементов из буфера в точку, 'заданную курсором. Shape-элементы вставляются, как обычно, 'не привязанные к фиксированной позиции. For Each Elem In MultBuffer If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If Next Elem End Sub

    Листинг 2.16.

    (html, txt)

    На этом я завершу рассмотрение способов создания буфера.


    Private Sub UserForm_Activate() Dim Txt As String NumElem = 0 For Each Elem In MultBuffer 'Анализ типа элемента буфера и создание элемента списка If Elem.Characters.Count = 1 Then 'Это объект Shape, InlineShape, 'специальный или однобуквенный символ! NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Elem.Text, 40) End If 'Добавление элемента в список формы ListBox1.AddItem Txt Next Elem

    End Sub

    Листинг 2.7.

    В цикле по объектам коллекции, составляющим буфер, анализируется их тип, и создаются текстовые элементы, представляющие элементы списка нашей формы. Заметьте, я достаточно просто расправляюсь с проблемой распознавания типа объекта, хранящегося в буфере. Для распознавания я использую следующий факт, - для всех рисованных объектов, объектов WordArt и подобных им объектов документа Word, свойство текст задается одним специальным символом. Поэтому я применяю следующее правило, - все объекты буфера, для которых свойство Characters возвращает коллекцию символов, содержащую ровно один символ, относятся к нетекстовым объектам или не могут, я полагаю, быть распознанными пользователем по этому символу. Поэтому такие объекты должны отображаться в списке словом "Объект" с соответствующим номером. Под это определение подпадают объекты Shape и InlineShape, специальные символы и, конечно, просто однобуквенные символы. Конечно, можно было бы предусмотреть более сложный анализ, но и принятое решение представляется вполне разумным и вполне удовлетворительно, как мне кажется, на практике. Некоторое сомнение может возникать в тех случаях, когда действительно копируются однобуквенные символы, но, заметьте, специальные символы целесообразно считать объектами, а простые символы вряд ли стоить копировать в множественный буфер. Далее я все-таки покажу, как можно было бы более детально проанализировать тип объекта, хранящегося в буфере.

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


    Public Sub PasteMult() 'Показывает форму с элементами буфера BufferForm.Show End Sub

    Листинг 2.8.

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

    Private Sub CommandButton1_Click() InsertElem End Sub

    Public Sub InsertElem() 'Вставляет выбранный элемент буфера 'в точку, заданную курсором Dim RowIndex As Integer Dim Sel As Boolean Sel = False With BufferForm.ListBox1 For RowIndex = 0 To .ListCount - 1 If .Selected(RowIndex) Then Sel = True Set Elem = MultBuffer(RowIndex + 1) If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If Exit For End If Next RowIndex If Not Sel Then MsgBox ("Для вставки выберете элемент из списка!") End If End With End Sub

    Листинг 2.9.

    Несколько слов о том, как реализована вставка. Вначале я определяю, какой элемент списка выбран, что позволяет определить индекс элемента буфера (коллекции), который должен быть вставлен в позицию курсора. На этом этапе также приходится анализировать тип вставляемого объекта, поскольку объекты Shape не могут быть вставлены в позицию, заданную курсором. Более того, они не могут быть скопированы стандартным способом через буфер, для них приходится применять метод Duplicate, специально предназначенный для этих целей. Заметьте, в данной ситуации необходимо более корректно анализировать тип объекта, находящегося в буфере. Для распознавания того, что объект буфера принадлежит классу Shape, я вызываю метод RangeShape, возвращающий коллекцию Shapes объекта Range. Если эта коллекция не пуста, то имеем дело с объектом Shape.

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

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


    Private Sub CommandButton2_Click() DelElem End Sub

    Public Sub DelElem() 'Удаляет выбранный элемент буфера

    Dim RowIndex As Integer Dim Sel As Boolean Sel = False With BufferForm.ListBox1 For RowIndex = 0 To .ListCount - 1 If .Selected(RowIndex) Then Sel = True MultBuffer.Remove (RowIndex + 1) .RemoveItem RowIndex Exit For End If RowIndex = RowIndex + 1 Next RowIndex If Not Sel Then MsgBox ("Для удаления выберете элемент из списка!") End If End With End Sub

    Листинг 2.10.

    Заметьте, выбранный элемент удаляется как из списка, так и из буфера

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

    Private Sub CommandButton3_Click() ClearAll End Sub

    Public Sub ClearAll() 'Удаляет элементы из буфера (коллекции) Dim i As Integer For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i 'Удаляет элементы из списка For i = 1 To BufferForm.ListBox1.ListCount BufferForm.ListBox1.RemoveItem (0) Next i BufferForm.Hide End Sub

    Листинг 2.11.

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


    Public Function ExistCommandBar( PanelName As String) As Boolean 'Возвращает True, если в коллекции CommandBars 'существует панель с именем PanelName Dim bar As CommandBar, Exist As Boolean Exist = False For Each bar In CommandBars If bar.name = PanelName Or bar.NameLocal = PanelName Then Exist = True Exit For End If Next bar ExistCommandBar = Exist End Function

    Public Sub AddPanel(PanelName As String) 'Добавляет и делает видимой панель с именем Panelname 'в коллекцию Commandbars 'Панель расположена вверху документа, 'не заменяет главное меню и не является временной If Not ExistCommandBar(PanelName) Then Call CommandBars.Add(name:=PanelName, Position:=msoBarTop, _ MenuBar:=False, Temporary:=False) End If CommandBars(PanelName).Enabled = True CommandBars(PanelName).Visible = True

    End Sub

    Public Sub CreateComboPanel() 'Создание панели с элементами класса CommandBarCombobox 'Создаем панель Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox AddPanel ("MultBufferPanel") Set Panel = CommandBars("MultBufferPanel") 'Добавляем на панель Combo кнопку типа DropDown - выпадающий список Set Ctrl = AddCustomCombo(Panel, "DropdownItem", msoControlDropdown) 'Указываем обработчик события при выборе элемента списка Ctrl.OnAction = "DropdownReaction" End Sub Public Function ExistControl(Panel As CommandBar, _ Capt As String) As Boolean 'Возвращает True, если в коллекции Controls ' панели с именем Panel существует элемент с заголовком capt Dim Ctrl As CommandBarControl, Exist As Boolean Exist = False For Each Ctrl In Panel.Controls If Ctrl.Caption = Capt Then Exist = True Exit For End If Next Ctrl ExistControl = Exist End Function

    Public Function AddCustomCombo(Panel As CommandBar, _ name As String, tip As Variant) As CommandBarComboBox 'Добавляет на панель элемент, тип которого задан параметром tip 'возвращая объект CommandBarComboBox в качестве результата Dim Ctrl As CommandBarComboBox If Not ExistControl(Panel, name) Then Set Ctrl = Panel.Controls.Add(Type:=tip) Ctrl.Caption = name End If Set AddCustomCombo = Panel.Controls(name) End Function


    Листинг 2.12.

    Основной здесь является процедура CreateComboPanel, при запуске которой будет создана панель с именем MultBufferPanel, если она не была уже создана ранее, и на эту панель будет помещена кнопка типа Dropdown. Обратите внимание, что в этой процедуре задается обработчик события, которому я дал имя DropdownReaction, вызываемый в ответ на выбор пользователем элемента списка. Этот обработчик и будет выполнять основную операцию по вставке элемента буфера в позицию заданную курсором. Текст его приведу чуть позже, а сейчас замечу, что саму работу по добавлению данной кнопки на панель выполняет функция AddCustomCombo, которая не только создает кнопку, но и присваивает ей заголовок (Caption) "DropdownItem".

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

    Public Sub Copy1Mult() 'Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range 'Одновременно создается список элемента ComboBox 'на панели MultBufferPanel Dim Txt As String Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox If Selection.Range.Characters.Count = 1 Then NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Selection.Range.Text, 40) End If Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") 'Добавление элемента списка Ctrl.AddItem Txt End Sub

    Листинг 2.13.

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

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

    Public Sub DropdownReaction() 'Обработчик кнопки меню - DropDown ComboBox Dim Ctrl As CommandBarComboBox Set Ctrl = CommandBars.ActionControl


    Set Elem = MultBuffer(Ctrl.ListIndex) If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан ' к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If End Sub

    Листинг 2.14.

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

    Макрос DelAll выполняет чистку буфера и списка:

    Public Sub DelAll() 'Удаляет элементы из буфера и DropDown списка на панели Dim i As Integer Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox 'Удаляет элементы из буфера (коллекции) For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i 'Удаление из списка Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") For i = 1 To Ctrl.ListCount Ctrl.RemoveItem (1) Next i End Sub

    Листинг 2.15.

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

    Public Sub InsertAll() 'Вставка всех элементов из буфера в точку, 'заданную курсором. Shape-элементы вставляются, как обычно, 'не привязанные к фиксированной позиции. For Each Elem In MultBuffer If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If Next Elem End Sub

    Листинг 2.16.

    На этом я завершу рассмотрение способов создания буфера.

    Справочник "Кто есть кто" в компьютерном мире

    Задачей, которую собираюсь сейчас привести, я занимался пару лет назад. Известная фирма Dator ежегодно публиковала справочники "Кто есть кто" в компьютерном бизнесе. Готовился к изданию и электронный вариант этого справочника, а я предложил написать к этому варианту макрос, позволяющий пользователям выбрать из этой базы данных интересующих его персон и добавить их в папку "Контакты" приложения Outlook. Электронный вариант справочника так и не появился, и кнопка, преобразующая текстовую базу данных в контакты, так и не увидела свет. И хотя справочник, с которым я работал в то время, теперь уже устарел, но сама по себе задача, на мой взгляд, нисколько не устарела. Я с удовлетворением обнаружил, что, написанная для Office 97 программа прекрасно работает и в среде Office 2000, с удовольствием привожу ее в данной книге. В данном примере не рассматриваются классические задачи работы с электронным справочником по поиску, добавлению или удалению записей справочника, я занимаюсь экзотической, но не менее интересной задачей преобразования текстовой базы данных справочника "Кто есть кто" в базу данных приложения Outlook.

    Вариации на тему "буфера"

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

    Вариации на тему кодирования

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

  • Прежде чем приводить реализацию частных случаев скажем несколько слов об общей схеме решения задачи трансляции. Для ее решения достаточно построить два массива одинаковой длины Source и Dest, первый - массив исходных символов, требующих перевода, второй - массив строк, задающих перевод каждого символа. Dest(i) задает перевод символа Sourse(i). Разумно задавать Source в виде строки символов, упорядоченных в соответствии с их кодировкой. Если кодирование имеет тип "символ в символ", то и Dest представляется строкой символов. Пусть теперь Text - это исходный текст, подлежащий переводу, а TextResult - результирующий текст после первода. Алгоритм решения задачи трансляции выглядит следующим образом:
    TextResult = "" For Each Sym In Text Index = FindIndex(Sym, Source) TextResult = TextResult & Dest(Index) Next
    Листинг 2.17.
    (html, txt)
    Функция FindIndex определяет индекс вхождения символа Sym в строку Source. Эффективная ее реализация может использовать классический алгоритм бинарного поиска, имеющий сложность logM, где M - длина строки Source (количество символов исходного множества). Общая сложность алгоритма трансляции символов - NlogM, где N - длина текста. Учитывая, что, как правило, M < 256, общая сложность не превосходит 8N. Поиск индекса можно реализовать проще и эффективнее бинарного при условии, что исходное множество символов имеет плотную кодировку, то есть код следующего символа на 1 больше предыдущего. В этом случае используется встроенная функция Asс, возвращающая код символа.

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

    Добавление и удаление панелей и других элементов

    Начнем с процедуры, позволяющей в любом из приложений Office 2000 добавить в коллекцию CommandBarsсобственную инструментальную панель с заданным именем. Напомним, что хотя эта задача и решается одним оператором, поскольку у коллекции есть специальный метод Add, но корректная реализация требует предварительной проверки существования в коллекции панелей элемента с таким именем. Вот процедура, решающая эту задачу и использующая ранее написанную функцию Exist:
    Public Sub AddPanel(PanelName As String) 'Добавляет и делает видимой панель с именем Panelname 'в коллекцию Commandbars 'Панель расположена вверху документа, 'не заменяет главное меню и не является временной If Not ExistCommandBar(PanelName) Then Call CommandBars.Add(name:=PanelName, Position:=msoBarTop, _ MenuBar:=False, Temporary:=False) End If CommandBars(PanelName).Enabled = True CommandBars(PanelName).Visible = True
    End Sub
    Листинг 3.4.
    (html, txt)
    Говоря о коллекции CommandBars и добавлении пользовательских панелей в эту коллекцию, следует иметь в виду одно обстоятельство. В разных приложениях Office 2000 есть свои нюансы использования этих коллекций. В приложении Word можно рассматривать коллекцию панелей, связанную с конкретным документом, и общую коллекцию, связанную с проектом Normal. Поэтому при работе в приложении Word, при рассмотрении коллекции панелей, связанной с документом, следует пользоваться уточненным именем ActiveDocument.CommandBars. В противном случае речь будет идти об общей коллекции, связанной с проектом Normal. При работе в Excel все пользовательские панели будут автоматически добавляться в коллекцию, связанную с документом и не будут появляться при открытии других документов Excel. В приложении Outlook программный проект, частью которого является коллекция, существует в единственном экземпляре, аналогично проекту Normal. Поэтому добавляемые панели будут распространяться на все документы Outlook.
    Говоря о приложении Word и добавлении пользовательских панелей, хочу рассказать еще об одном небольшом "жучке" и как с ним бороться.
    В приложении Word невозможно программно добавить панель, связанную с документом. Добавляемая программно панель будет всегда связана с проектом Normal и, следовательно, будет доступна для всех документов Word, что не всегда является желательным. Как уже отмечалось, свойство Context, которое должно обеспечивать связывание панели с документом, корректно не работает. Поэтому, если добавляемую панель необходимо связать только с конкретным документом, то работу по добавлению панели необходимо проделать вручную.

    Начнем с процедуры, позволяющей в любом из приложений Office 2000 добавить в коллекцию CommandBarsсобственную инструментальную панель с заданным именем. Напомним, что хотя эта задача и решается одним оператором, поскольку у коллекции есть специальный метод Add, но корректная реализация требует предварительной проверки существования в коллекции панелей элемента с таким именем. Вот процедура, решающая эту задачу и использующая ранее написанную функцию Exist:
    Public Sub AddPanel(PanelName As String) 'Добавляет и делает видимой панель с именем Panelname 'в коллекцию Commandbars 'Панель расположена вверху документа, 'не заменяет главное меню и не является временной If Not ExistCommandBar(PanelName) Then Call CommandBars.Add(name:=PanelName, Position:=msoBarTop, _ MenuBar:=False, Temporary:=False) End If CommandBars(PanelName).Enabled = True CommandBars(PanelName).Visible = True
    End Sub
    Листинг 3.4.
    Говоря о коллекции CommandBars и добавлении пользовательских панелей в эту коллекцию, следует иметь в виду одно обстоятельство. В разных приложениях Office 2000 есть свои нюансы использования этих коллекций. В приложении Word можно рассматривать коллекцию панелей, связанную с конкретным документом, и общую коллекцию, связанную с проектом Normal. Поэтому при работе в приложении Word, при рассмотрении коллекции панелей, связанной с документом, следует пользоваться уточненным именем ActiveDocument.CommandBars. В противном случае речь будет идти об общей коллекции, связанной с проектом Normal. При работе в Excel все пользовательские панели будут автоматически добавляться в коллекцию, связанную с документом и не будут появляться при открытии других документов Excel. В приложении Outlook программный проект, частью которого является коллекция, существует в единственном экземпляре, аналогично проекту Normal. Поэтому добавляемые панели будут распространяться на все документы Outlook.
    Говоря о приложении Word и добавлении пользовательских панелей, хочу рассказать еще об одном небольшом "жучке" и как с ним бороться.
    В приложении Word невозможно программно добавить панель, связанную с документом. Добавляемая программно панель будет всегда связана с проектом Normal и, следовательно, будет доступна для всех документов Word, что не всегда является желательным. Как уже отмечалось, свойство Context, которое должно обеспечивать связывание панели с документом, корректно не работает. Поэтому, если добавляемую панель необходимо связать только с конкретным документом, то работу по добавлению панели необходимо проделать вручную.


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

    Public Sub AddMainPanel(MainPanel As String) 'Выключаем все панели документа Dim bar As CommandBar For Each bar In CommandBars bar.Enabled = False Next bar 'Добавляем и включаем новую панель главного меню If Not ExistCommandBar(MainPanel) Then Call CommandBars.Add(name:=MainPanel, Position:=msoBarTop, _ MenuBar:=True, Temporary:=False) End If CommandBars(MainPanel).Enabled = True CommandBars(MainPanel).Visible = True

    End Sub

    Листинг 3.5.

    (html, txt)

    Поскольку панели добавляются, то их нужно уметь и удалять, - операция Add должна иметь и обратную операцию Remove (Delete). Удаление возможно только для пользовательских панелей и невозможно, естественно, для встроенных панелей. Выполняется удаление достаточно просто, но и здесь желательны предварительные проверки:

    Листинг 3.6.

    (html, txt)

    Приведем еще одну обратную операцию. Поскольку в процедуре AddMainPanel все панели документа были отключены (это очень опасная операция), то необходимо иметь процедуру, восстанавливающую отключенные панели:

    Public Sub ResetMainMenu() Dim CstmBar As CommandBar 'Включаем все панели For Each CstmBar In CommandBars CstmBar.Enabled = True Next CstmBar

    Set CstmBar = CommandBars.Item("Menu Bar") CstmBar.Visible = True End Sub

    Листинг 3.7.

    (html, txt)

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

    Public Sub AddBuiltinMenu(Panel As String, MenuName As String, _ Num As Variant, item As CommandBarPopup) 'Добавляет встроенный пункт меню с именем MenuName 'на панель Panel Dim bar As CommandBar, ider As Variant Dim Ctrl As CommandBarControl Set bar = CommandBars(Panel) If Not ExistControl(bar, MenuName) Then Set Ctrl = MyFindControl(MenuName) 'Если найден If Not (Ctrl Is Nothing) Then ider = Ctrl.ID bar.Controls.Add Type:=msoControlPopup, _ ID:=ider, Before:=Num Else: Exit Sub End If End If Set item = bar.Controls(MenuName) End Sub


    Листинг 3.8.

    (html, txt)

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

    Public Sub AddItem(item As CommandBarPopup, name As String) Dim Ctrl As CommandBarControl If Not ExistItem(item, name) Then 'Найти встроенный элемент в коллекции встроенных панелей Set Ctrl = MyFindControl(name) 'Если найден If Not (Ctrl Is Nothing) Then item.Controls.Add Type:=msoControlButton, ID:=Ctrl.ID End If End If End Sub

    Листинг 3.9.

    (html, txt)

    Приведем еще парочку процедур добавления:

    Листинг 3.10.

    (html, txt)

    on_load_lecture()

    Добавление и удаление панелей и других элементов
    Добавление и удаление панелей и других элементов
    Дальше "

    Добавление и удаление панелей и других элементов
    Если Вы заметили ошибку - сообщите нам.
    Добавление и удаление панелей и других элементов
    Страницы:

    " |

    1

    |

    2

    |

    3

    |

    4

    |

    вопросы | "

    |

    для печати и PDA

    Добавление и удаление панелей и других элементов
    Добавление и удаление панелей и других элементов
    Добавление и удаление панелей и других элементов
    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование



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

    Public Sub AddMainPanel(MainPanel As String) 'Выключаем все панели документа Dim bar As CommandBar For Each bar In CommandBars bar.Enabled = False Next bar 'Добавляем и включаем новую панель главного меню If Not ExistCommandBar(MainPanel) Then Call CommandBars.Add(name:=MainPanel, Position:=msoBarTop, _ MenuBar:=True, Temporary:=False) End If CommandBars(MainPanel).Enabled = True CommandBars(MainPanel).Visible = True

    End Sub

    Листинг 3.5.

    Поскольку панели добавляются, то их нужно уметь и удалять, - операция Add должна иметь и обратную операцию Remove (Delete). Удаление возможно только для пользовательских панелей и невозможно, естественно, для встроенных панелей. Выполняется удаление достаточно просто, но и здесь желательны предварительные проверки:

    Public Sub DelPanel(PanelName As String) 'Удаляет пользовательскую панель 'из коллекции CommandBars If ExistCommandBar(PanelName) _ And Not CommandBars(PanelName).BuiltIn Then CommandBars(PanelName).Delete End If End Sub

    Листинг 3.6.

    Приведем еще одну обратную операцию. Поскольку в процедуре AddMainPanel все панели документа были отключены (это очень опасная операция), то необходимо иметь процедуру, восстанавливающую отключенные панели:

    Public Sub ResetMainMenu() Dim CstmBar As CommandBar 'Включаем все панели For Each CstmBar In CommandBars CstmBar.Enabled = True Next CstmBar

    Set CstmBar = CommandBars.Item("Menu Bar") CstmBar.Visible = True End Sub

    Листинг 3.7.

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


    Public Sub AddBuiltinMenu( Panel As String, MenuName As String, _ Num As Variant, item As CommandBarPopup) 'Добавляет встроенный пункт меню с именем MenuName 'на панель Panel Dim bar As CommandBar, ider As Variant Dim Ctrl As CommandBarControl Set bar = CommandBars(Panel) If Not ExistControl(bar, MenuName) Then Set Ctrl = MyFindControl(MenuName) 'Если найден If Not (Ctrl Is Nothing) Then ider = Ctrl.ID bar.Controls.Add Type:=msoControlPopup, _ ID:=ider, Before:=Num Else: Exit Sub End If End If Set item = bar.Controls(MenuName) End Sub

    Листинг 3.8.

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

    Public Sub AddItem(item As CommandBarPopup, name As String) Dim Ctrl As CommandBarControl If Not ExistItem(item, name) Then 'Найти встроенный элемент в коллекции встроенных панелей Set Ctrl = MyFindControl(name) 'Если найден If Not (Ctrl Is Nothing) Then item.Controls.Add Type:=msoControlButton, ID:=Ctrl.ID End If End If End Sub

    Листинг 3.9.

    Приведем еще парочку процедур добавления:

    Public Sub AddCustomItem(item As CommandBarPopup, _ name As String, Act As String) 'Добавляет команду меню 'Параметр Act задает имя макроса, реализующего команду Dim Ctrl As CommandBarControl If Not ExistItem(item, name) Then Set Ctrl = item.Controls.Add(Type:=msoControlButton) Ctrl.Caption = name Ctrl.OnAction = Act End If End Sub Public Function AddCustomPopup(item As CommandBarPopup, _ name As String) As CommandBarPopup 'Добавляет подменю в меню, 'возвращая объект подменю в качестве результата Dim Ctrl As CommandBarPopup If Not ExistItem(item, name) Then Set Ctrl = item.Controls.Add(Type:=msoControlPopup) Ctrl.Caption = name End If Set AddCustomPopup = item.Controls(name) End Function

    Листинг 3.10.

    Exist-функции

    К сожалению, у объектов CommandBars, CommandBar, CommandBarPopUp отсутствует весьма полезное свойство, проверяющее существование элемента в коллекции, на панели или меню. Оно необходимо при добавлении элемента, поскольку прежде чем добавить элемент, следует проверить, не существует ли он уже в коллекции. Эта проверка необходима еще и потому, что добавление выполняется вне зависимости от присутствия добавляемого элемента в коллекции (панели, меню) и тем самым порождает копии элементов, что, конечно же, нежелательно. Я написал три функции, реализующие свойство Exist у рассматриваемых объектов:
    Листинг 3.1.
    (html, txt)
    Первая из них проверяет существование панели с заданным именем в коллекции панелей. Обратите внимание, имя можно задавать и по-русски, поскольку проверяются оба свойства Name и NameLocal. Вторая функция проверяет на заданной панели существование элемента с указанным заголовком. Третья функция выполняет аналогичную операцию для меню - объекта CommandBarPopUp.

    Коллекция CommandBarControls и ее элементы

    В эту коллекцию входят элементы, располагаемые на одной инструментальной панели. Коллекция обладает только традиционными свойствами и методами. Единственное, что следует рассмотреть - это метод Add и его параметры:
    Function Add([Type], [Id], [Parameter], [Before], [Temporary]) As CommandBarControl
    Метод позволяет добавлять новые элементы в коллекцию. Его параметры:
  • Type устанавливает тип элемента и класс этого элемента, возвращаемый функцией Add. Если значением Type является константа msoControlButton, то возвращается объект класса CommandBarButton, для значений: msoControlEdit, msoControlDropdown, msoControlComboBox - возвращается объект класса CommandBarComboBox и, наконец, если тип имеет значение msoControlPopup, то возвращаемый класс объекта - CommandBarPopup
  • Id задает идентификатор встроенного элемента, добавляемого в коллекцию. Если он опущен, то на панели появится пользовательский, пустой элемент, свойства которого позднее следует определить.
  • Parameter для встроенных элементов используется приложением при запуске команды, для пользовательских элементов может хранить некоторую информацию об элементе.
  • Before задает местоположение элемента на панели, если он опущен, элемент добавляется в конец панели.
  • Temporary является булевым параметром. Его значение True говорит о том, что элемент является временным и будет удален с панели при закрытии приложения. По умолчанию элементы являются постоянными.

  • Хотя все создаваемые элементы панели относятся к одному из трех различных классов, все они принадлежат также объединяющему классу CommandBarControl. Мы не будем подробно останавливаться на всех свойствах и методах, как отдельных классов, так и объединяющего элементы класса. Рассмотрим только, как решить главную для команд задачу - выполнить некоторую процедуру в ответ на выбор команды меню или щелчок кнопки. Действия, выполняемые командой, должны быть запрограммированы в виде макроса на языке VBA. Напомним, что макрос - это процедура без параметров. Однако есть некоторый способ передачи информации в исполняемый макрос. Для этого можно использовать параметр Parameter, задаваемый при создании элемента или, что удобнее, свойство Parameter. Конечно, для всех встроенных элементов макрос, задающий команду, уже написан. Но для собственных элементов его нужно написать самому, а затем макрос связать с элементом. Свойство OnAction, которым обладают все элементы панели, позволяет связать элемент с исполняемым макросом. Формально свойство является строкой, задающей имя макроса, который и будет вызываться в ответ на выбор пользователя.
    Давайте перейдем теперь к рассмотрению задач, возникающих при создании интерфейса. Я начну c рассмотрения процедур, реализующих некоторые дополнительные свойства изучаемых нами объектов.

    Поиск элементов

    Казалось бы, что с поиском элементов проблем нет, поскольку объекты CommandBars и Commandbar имеют метод FindControl, а в Office 2000 появился и новый метод FindControls. Эти методы, к сожалению, не всегда применимы, поскольку имеют весьма существенный недостаток. Ключом поиска является значение ID или Tag, но в момент поиска значения этих свойств, как правило, не известно. Чаще всего, известен лишь заголовок разыскиваемого элемента, не включенный в число параметров стандартных методов поиска. Поэтому я предпочел написать собственную процедуру поиска. Приведу эту процедуру и заодно расскажу об одной не очень приятной особенности коллекции панелей CommandBars:
    Листинг 3.2.
    (html, txt)
    Эта функция похожа на функцию FindControl коллекции CommandBars, также в случае успеха возвращается первый найденный элемент класса CommandBarControl. Разница состоит в том, что ключом поиска является заголовок искомого элемента. Но я хочу обратить внимание на реализацию данного метода. Заметьте, несмотря на то, что в основной части этой функции организован перебор по всей коллекции CommandBars, этому поиску предшествует поиск на отдельных элементах этой коллекции. Дело в том, что не все панели, реально присутствующие в коллекции, могут быть получены путем перебора в стандартном цикле For Each, индексы этих элементов выходят за диапазон, заданный значением Count этой коллекции. Таким образом, можно получить, например, доступ к панели "File"
    Листинг 3.3.
    (html, txt)
    Но нельзя получить этот элемент в процессе перебора элементов коллекции CommandBars. Вместе с тем, многие команды меню находятся именно на этих панелях. Так, команда меню "Сохранить как " присутствует только на панели "File", в то же время другие команды, как, например, "Открыть", находятся одновременно и на панели "Standard". Справедливости ради, стоит заметить, что, если Вы знаете ID элемента, то для его поиска можно использовать стандартный метод поиска FindControl, который ведет поиск на всех панелях коллекции. Если же перебор панелей организуется самостоятельно, то следует считаться с "жучком", не позволяющим автоматически получить все элементы коллекции CommandBars.

    PanelName As String) As Boolean

    Public Function ExistCommandBar( PanelName As String) As Boolean 'Возвращает True, если в коллекции CommandBars 'существует панель с именем PanelName Dim bar As CommandBar, Exist As Boolean Exist = False For Each bar In CommandBars If bar.Name = PanelName Or bar.NameLocal = PanelName Then Exist = True Exit For End If Next bar ExistCommandBar = Exist End Function Public Function ExistControl(Panel As CommandBar, _ Capt As String) As Boolean 'Возвращает True, если в коллекции Controls ' панели с именем Panel существует элемент с заголовком capt Dim Ctrl As CommandBarControl, Exist As Boolean Exist = False For Each Ctrl In Panel.Controls If Ctrl.Caption = Capt Then Exist = True Exit For End If Next Ctrl ExistControl = Exist End Function Public Function ExistItem(CtrlPopUp As CommandBarPopup, _ Capt As String) As Boolean 'Возвращает True, если в меню CtrlPopUp ' существует элемент с заголовком capt Dim Ctrl As CommandBarControl, Exist As Boolean Exist = False For Each Ctrl In CtrlPopUp.Controls If Ctrl.Caption = Capt Then Exist = True Exit For End If Next Ctrl ExistItem = Exist End Function
    Листинг 3.1.
    Закрыть окно

    _ name As String, Act

    Public Sub AddCustomItem(item As CommandBarPopup, _ name As String, Act As String) 'Добавляет команду меню 'Параметр Act задает имя макроса, реализующего команду Dim Ctrl As CommandBarControl If Not ExistItem(item, name) Then Set Ctrl = item.Controls.Add(Type:=msoControlButton) Ctrl.Caption = name Ctrl.OnAction = Act End If End Sub Public Function AddCustomPopup(item As CommandBarPopup, _ name As String) As CommandBarPopup 'Добавляет подменю в меню, 'возвращая объект подменю в качестве результата Dim Ctrl As CommandBarPopup If Not ExistItem(item, name) Then Set Ctrl = item.Controls.Add(Type:=msoControlPopup) Ctrl.Caption = name End If Set AddCustomPopup = item.Controls(name) End Function
    Листинг 3.10.
    Закрыть окно

    Создает главное меню Dim newitem

    Public Sub CreateMainMenu() ' Создает главное меню Dim newitem As CommandBarPopup Dim Popupitem As CommandBarPopup 'Создает панель MainPanel главного меню AddMainPanel ("MainPanel") 'Добавляет пункт File Call AddBuiltinMenu("MainPanel", "&Файл", 1, newitem) AddItem newitem, "Созд&ать" AddItem newitem, "&Открыть..." AddItem newitem, "&Закрыть" AddItem newitem, "&Сохранить" AddItem newitem, "&Печать" AddItem newitem, "Сохранить &как..." 'Добавляет пункт Edit Call AddBuiltinMenu("MainPanel", "&Правка", 2, newitem) AddItem newitem, "&Вырезать" AddItem newitem, "&Копировать" AddItem newitem, "Вст&авить" AddItem newitem, "&Найти..." AddItem newitem, "&Заменить..." 'Добавляет собственный пункт "Документы" Call AddMenu("MainPanel", "&Документы", 3, newitem) AddCustomItem newitem, "Все документы", "Sorry" AddCustomItem newitem, "Мои документы", "Sorry" 'Добавляем подменю Set Popupitem = AddCustomPopup(newitem, "Текущие документы") AddCustomItem Popupitem, "Все текущие документы", "Sorry" AddCustomItem Popupitem, "Мои текущие документы", "Sorry" AddCustomItem newitem, "Последний документ", "Sorry"
    'Добавляет собственный пункт "Проекты" Call AddMenu("MainPanel", "&Проекты", 4, newitem) AddCustomItem newitem, "Все проекты", "Sorry" AddCustomItem newitem, "Мои проекты", "Sorry" 'Добавляем подменю Set Popupitem = AddCustomPopup(newitem, "Текущие проекты") AddCustomItem Popupitem, "Все текущие проекты", "Sorry" AddCustomItem Popupitem, "Мои текущие проекты", "Sorry" AddCustomItem newitem, "Последний проект", "Sorry"
    'Добавляет собственный пункт "Презентации" Call AddMenu("MainPanel", "&Презентации", 5, newitem) AddCustomItem newitem, "Все презентации", "Sorry" AddCustomItem newitem, "Мои презентации", "Sorry" 'Добавляем подменю Set Popupitem = AddCustomPopup(newitem, "Текущие презентации") AddCustomItem Popupitem, "Все текущие презентации", "Sorry" AddCustomItem Popupitem, "Мои текущие презентации", "Sorry" AddCustomItem newitem, "Последняя презентация", "Sorry"
    End Sub
    Листинг 3.11.
    Закрыть окно

    Создаем панель Dim panel As

    Public Sub CreateComboPanel() 'Создание панели с элементами класса CommandBarCombobox ' Создаем панель Dim panel As CommandBar Dim Ctrl As CommandBarComboBox AddPanel ("ComboPanel") Set panel = CommandBars("ComboPanel") 'Добавляем на панель три Combo кнопки разного типа 'Добавление окна редактирования Edit Set Ctrl = AddCustomCombo(panel, "Edititem", msoControlEdit) 'Добавляем text в окно редактирования Ctrl.Text = "Один" Ctrl.OnAction = "EditReaction" 'Добавление выпадающего списка Set Ctrl = AddCustomCombo(panel, "DropdownItem", msoControlDropdown) 'Задание элементов списка Ctrl.AddItem "One", 1 Ctrl.AddItem "Two", 2 Ctrl.AddItem "Three", 3 'Отчеркивание группы элементов Ctrl.ListHeaderCount = 3 'Добавление элемента в конец списка Ctrl.AddItem "Others" Ctrl.OnAction = "DropdownReaction" 'Добавление элемента Combobox - окно редактирования и список Set Ctrl = AddCustomCombo(panel, "ComboBoxItem", msoControlComboBox) Ctrl.Text = "One" Ctrl.AddItem "One" Ctrl.AddItem "Five" Ctrl.OnAction = "DropdownReaction" End Sub
    Листинг 3.12.
    Закрыть окно

    _ name As String, tip

    Public Function AddCustomCombo(panel As CommandBar, _ name As String, tip As Variant) As CommandBarComboBox 'Добавляет на панель элемент, тип которого задан параметром tip 'возвращая объект CommandBarComboBox в качестве результата Dim Ctrl As CommandBarComboBox If Not ExistControl(panel, name) Then Set Ctrl = panel.Controls.Add(Type:=tip) Ctrl.Caption = name End If Set AddCustomCombo = panel.Controls(name) End Function
    Листинг 3.13.
    Закрыть окно

    Edit ComboBox Dim Ctrl As

    Public Sub EditReaction() 'Обработчик меню - Edit ComboBox Dim Ctrl As CommandBarComboBox 'Определяем активный элемент коллекции панелей, 'задающий меню, выбранное пользователем Set Ctrl = CommandBars.ActionControl MsgBox ("Привет! В окне редактирования набран (выбран) текст: " & Ctrl.Text)
    'Далее можно задать реакцию на введенный текст
    End Sub
    Public Sub DropdownReaction() 'Обработчик кнопки меню - Dropdown ComboBox Dim Ctrl As CommandBarComboBox Set Ctrl = CommandBars.ActionControl 'MsgBox ("Hi! Here is Dropdown Box") With Ctrl Select Case .Text Case "One", "Two" MsgBox ("Your Choice : one or two") Case "Three" MsgBox ("Your Choice : three") Case Else MsgBox ("Your Choice : unknown") End Select
    Select Case .ListIndex Case 1, 2 MsgBox ("Your Choice : one or two") Case 3 MsgBox ("Your Choice : three") Case Else MsgBox ("Your Choice : unknown") End Select
    End With End Sub
    Листинг 3.14.
    Закрыть окно

    Создаем панель Dim panel As

    Public Sub CreateButtonPanel() 'Создание панели с элементами класса CommandBarButton ' Создаем панель Dim panel As CommandBar Dim ctrl As CommandBarButton Dim Ctrl1 As CommandBarButton AddPanel ("ButtonPanel") Set panel = CommandBars("ButtonPanel") 'Добавляем на панель четыре кнопки 'Добавление кнопки ER Set ctrl = AddCustomButton(panel, "ER") 'Свойства кнопки With ctrl .OnAction = "Translate.FromEtoR" .DescriptionText = _ "Перевод в русскую раскладку клавиатуры" .TooltipText = "English -> Russian" .Style = msoButtonIconAndCaption .Tag = "English -> Russian" .FaceId = 134 End With 'Добавление кнопки RE Set ctrl = AddCustomButton(panel, "RE") 'Свойства кнопки With ctrl .OnAction = "Translate.FromRtoE" .DescriptionText = _ "Перевод в английскую раскладку клавиатуры" .TooltipText = "Russian -> English" .Style = msoButtonIconAndCaption .Tag = "Russian -> English" .FaceId = 135 End With 'Добавление кнопки RL Set ctrl = AddCustomButton(panel, "RL") 'Свойства кнопки With ctrl .OnAction = "Translate.FromRuToLat" .DescriptionText = _ "Перевод в латиницу" .TooltipText = "Кириллица -> Латиница" .Style = msoButtonIconAndCaption .Tag = "Кириллица -> Латиница" .FaceId = 137 End With 'Добавление кнопки RepNew Set ctrl = AddCustomButton(panel, "RepNew") 'Свойства кнопки With ctrl .OnAction = "Translate.RepNew" .DescriptionText = _ "Форматирование программного текста" .TooltipText = "Форматирование" .Style = msoButtonIconAndCaption .Tag = "Форматирование программного текста" .BeginGroup = True 'Поиск кнопки с подходящим рисунком Set Ctrl1 = MyFindControl("&Разметка страницы") Ctrl1.CopyFace .PasteFace End With End Sub
    Листинг 3.15.
    Закрыть окно

    _ name As String) As

    Public Function AddCustomButton(panel As CommandBar, _ name As String) As CommandBarButton 'Добавляет на панель кнопку, 'возвращая объект CommandBarButton в качестве результата Dim ctrl As CommandBarButton If Not ExistControl(panel, name) Then Set ctrl = panel.Controls.Add(Type:=msoControlButton) ctrl.Caption = name End If Set AddCustomButton = panel.Controls(name) End Function
    Листинг 3.16.
    Закрыть окно

    Capt As String) As CommandBarControl

    Public Function MyFindControl( Capt As String) As CommandBarControl 'Поиск по заголовку 'Возвращает в качестве результата первый элемент с указанным заголовком Dim bar As CommandBar Dim Ctr As CommandBarControl Dim i As Integer For i = 1 To 9 Select Case i Case 1 Set bar = CommandBars("File") Case 2 Set bar = CommandBars("Edit") Case 3 Set bar = CommandBars("View") Case 4 Set bar = CommandBars("Insert") Case 5 Set bar = CommandBars("Format") Case 6 Set bar = CommandBars("Tools") Case 7 Set bar = CommandBars("Table") Case 8 Set bar = CommandBars("Window") Case 9 Set bar = CommandBars("Help") End Select For Each Ctr In bar.Controls If Ctr.Caption = Capt Then Set MyFindControl = Ctr Exit Function End If Next Ctr Next i For Each bar In CommandBars 'Debug.Print bar.name For Each Ctr In bar.Controls If Ctr.Caption = Capt Then Set MyFindControl = Ctr Exit Function End If Next Ctr Next bar End Function
    Листинг 3.2.
    Закрыть окно

    Set bar

    Set bar = CommandBars("File")
    Листинг 3.3.
    Закрыть окно

    и не является временной If

    Public Sub AddPanel(PanelName As String) 'Добавляет и делает видимой панель с именем Panelname 'в коллекцию Commandbars 'Панель расположена вверху документа, 'не заменяет главное меню и не является временной If Not ExistCommandBar(PanelName) Then Call CommandBars.Add(name:=PanelName, Position:=msoBarTop, _ MenuBar:=False, Temporary:=False) End If CommandBars(PanelName).Enabled = True CommandBars(PanelName).Visible = True
    End Sub
    Листинг 3.4.
    Закрыть окно

    Выключаем все панели документа Dim

    Public Sub AddMainPanel(MainPanel As String) ' Выключаем все панели документа Dim bar As CommandBar For Each bar In CommandBars bar.Enabled = False Next bar 'Добавляем и включаем новую панель главного меню If Not ExistCommandBar(MainPanel) Then Call CommandBars.Add(name:=MainPanel, Position:=msoBarTop, _ MenuBar:=True, Temporary:=False) End If CommandBars(MainPanel).Enabled = True CommandBars(MainPanel).Visible = True
    End Sub
    Листинг 3.5.
    Закрыть окно

    из коллекции CommandBars If

    Public Sub DelPanel(PanelName As String) 'Удаляет пользовательскую панель ' из коллекции CommandBars If ExistCommandBar(PanelName) _ And Not CommandBars(PanelName).BuiltIn Then CommandBars(PanelName).Delete End If End Sub
    Листинг 3.6.
    Закрыть окно

    Включаем все панели For Each

    Public Sub ResetMainMenu() Dim CstmBar As CommandBar ' Включаем все панели For Each CstmBar In CommandBars CstmBar.Enabled = True Next CstmBar
    Set CstmBar = CommandBars.Item("Menu Bar") CstmBar.Visible = True End Sub
    Листинг 3.7.
    Закрыть окно

    Panel As String, MenuName As

    Public Sub AddBuiltinMenu( Panel As String, MenuName As String, _ Num As Variant, item As CommandBarPopup) 'Добавляет встроенный пункт меню с именем MenuName 'на панель Panel Dim bar As CommandBar, ider As Variant Dim Ctrl As CommandBarControl Set bar = CommandBars(Panel) If Not ExistControl(bar, MenuName) Then Set Ctrl = MyFindControl(MenuName) 'Если найден If Not (Ctrl Is Nothing) Then ider = Ctrl.ID bar.Controls.Add Type:=msoControlPopup, _ ID:=ider, Before:=Num Else: Exit Sub End If End If Set item = bar.Controls(MenuName) End Sub
    Листинг 3.8.
    Закрыть окно

    item As CommandBarPopup, name As

    Public Sub AddItem( item As CommandBarPopup, name As String) Dim Ctrl As CommandBarControl If Not ExistItem(item, name) Then 'Найти встроенный элемент в коллекции встроенных панелей Set Ctrl = MyFindControl(name) 'Если найден If Not (Ctrl Is Nothing) Then item.Controls.Add Type:=msoControlButton, ID:=Ctrl.ID End If End If End Sub
    Листинг 3.9.
    Закрыть окно

    Пример создания интерфейса

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

    Проектирование панели с элементами Combobox

    Макросы, отвечающие за вызов команд меню, встроенных или собственных, могут быть сколь угодно сложными. Зачастую, выбор команды меню является, по существу, приглашением к началу диалога с пользователем. Соответствующий макрос открывает форму (диалоговое окно), содержащее многочисленные кнопки, вкладки и другие элементы интерфейса. Типичным примером являются встроенные команды "Открыть…" и "Сохранить как…", диалоговые окна которых пополнились новыми возможностями в Office 2000. Но не всегда следует применять формы, чтобы организовать диалог с пользователем в процессе выбора команды меню. Нередко можно обойтись специальными элементами класса CommandBarCombobox, позволяющими пользователю при работе с меню задать самостоятельно или выбрать подходящее значение из раскрывающегося списка. Примеры таких элементов хорошо знакомы по встроенному меню. С ними приходится, например, встречаться при выборе стиля, шрифта, подходящего размера шрифта или масштаба.
    Давайте создадим панель с собственными элементами подобного типа. Я помещу на панель три элемента разных типов, но принадлежащих одному классу - CommandBarComboBox:
  • Первый элемент будет представлять окно редактирования Edit. При работе с таким элементом пользователь имеет возможность ввести свой текст в окно редактирования или согласиться с предложенным ему текстом. Макрос, вызываемый в ответ на действия пользователя, может проанализировать текст, введенный пользователем и реагировать на это соответствующим образом.
  • Второй элемент будет представлять выпадающий список (Dropdown List), из которого пользователь может выбрать подходящее значение. На примере я покажу, как можно задать элементы списка и как в макросе, вызываемом в ответ на выбор пользователя, можно проанализировать, какое значение выбрал пользователь из списка.
  • Третий элемент - ComboBox - объединяет достоинства двух предыдущих элементов, обладая как окном редактирования, так и возможностью выбора значения из списка.

  • Вот текст процедуры, решающей поставленную задачу:

    Листинг 3.14.

    (html, txt)

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

    С окном редактирования все достаточно просто, поскольку уже знакомое свойство Text позволяет понять, какое значение введено пользователем или он согласился с предложенным ему значением. Для списков, как правило, устраивается разбор случаев. Заметьте, я привел два альтернативных способа, позволяющих узнать, какой элемент списка был выбран пользователем. В первом случае используется свойство текст. Альтернативой является использование свойства ListIndex, фиксирующего номер элемента списка, выбранного пользователем. Замечу, что часто приходится пользоваться и свойством List(i), хранящим значение i-го элемента списка.

    Public Sub CreateComboPanel() 'Создание панели с элементами класса CommandBarCombobox ' Создаем панель Dim panel As CommandBar Dim Ctrl As CommandBarComboBox AddPanel ("ComboPanel") Set panel = CommandBars("ComboPanel") 'Добавляем на панель три Combo кнопки разного типа 'Добавление окна редактирования Edit Set Ctrl = AddCustomCombo(panel, "Edititem", msoControlEdit) 'Добавляем text в окно редактирования Ctrl.Text = "Один" Ctrl.OnAction = "EditReaction" 'Добавление выпадающего списка Set Ctrl = AddCustomCombo(panel, "DropdownItem", msoControlDropdown) 'Задание элементов списка Ctrl.AddItem "One", 1 Ctrl.AddItem "Two", 2 Ctrl.AddItem "Three", 3 'Отчеркивание группы элементов Ctrl.ListHeaderCount = 3 'Добавление элемента в конец списка Ctrl.AddItem "Others" Ctrl.OnAction = "DropdownReaction" 'Добавление элемента Combobox - окно редактирования и список Set Ctrl = AddCustomCombo(panel, "ComboBoxItem", msoControlComboBox) Ctrl.Text = "One" Ctrl.AddItem "One" Ctrl.AddItem "Five" Ctrl.OnAction = "DropdownReaction" End Sub

    Листинг 3.12.

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

    Public Function AddCustomCombo(panel As CommandBar, _ name As String, tip As Variant) As CommandBarComboBox 'Добавляет на панель элемент, тип которого задан параметром tip 'возвращая объект CommandBarComboBox в качестве результата Dim Ctrl As CommandBarComboBox If Not ExistControl(panel, name) Then Set Ctrl = panel.Controls.Add(Type:=tip) Ctrl.Caption = name End If Set AddCustomCombo = panel.Controls(name) End Function

    Листинг 3.13.

    Хотя в процедуре CreateComboPanel есть подробные комментарии, но есть смысл сказать о ней еще несколько слов и остановится на используемых в ней методах и свойствах объектов. Это тем более уместно по той причине, что ранее я не стал описывать свойства и методы объектов класса CommandBarComboBox, ни их общего класса CommandbarControl. Теперь пришла пора в какой-то мере исправить это положение. Прежде всего, напомню, что объекты класса CommandbarComboBox могут быть разного типа. Их тип задается в момент создания элемента и определяется значением соответствующей константы. Так тип наших трех элементов задается соответственно константами: msoControlEdit, msoControlDropdown, msoControlComboBox. Тип определяет, какие свойства и методы класса CommandBarComboBox доступны для использования у данного элемента.

    Так, например, я использую свойство Text, чтобы задать текст, появляющийся в окне редактирования у первого и третьего элементов, которые обладают окном редактирования, доступным для ввода значений. Это свойство нельзя задать для второго элемента, задающего выпадающий список. Для него значение свойства Text формируется автоматически при выборе пользователем значения из списка.

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

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


    Public Sub EditReaction() 'Обработчик меню - Edit ComboBox Dim Ctrl As CommandBarComboBox 'Определяем активный элемент коллекции панелей, 'задающий меню, выбранное пользователем Set Ctrl = CommandBars.ActionControl MsgBox ("Привет! В окне редактирования набран (выбран) текст: " & Ctrl.Text)

    'Далее можно задать реакцию на введенный текст

    End Sub

    Public Sub DropdownReaction() 'Обработчик кнопки меню - Dropdown ComboBox Dim Ctrl As CommandBarComboBox Set Ctrl = CommandBars.ActionControl 'MsgBox ("Hi! Here is Dropdown Box") With Ctrl Select Case .Text Case "One", "Two" MsgBox ("Your Choice : one or two") Case "Three" MsgBox ("Your Choice : three") Case Else MsgBox ("Your Choice : unknown") End Select

    Select Case .ListIndex Case 1, 2 MsgBox ("Your Choice : one or two") Case 3 MsgBox ("Your Choice : three") Case Else MsgBox ("Your Choice : unknown") End Select

    End With End Sub

    Листинг 3.14.

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

    С окном редактирования все достаточно просто, поскольку уже знакомое свойство Text позволяет понять, какое значение введено пользователем или он согласился с предложенным ему значением. Для списков, как правило, устраивается разбор случаев. Заметьте, я привел два альтернативных способа, позволяющих узнать, какой элемент списка был выбран пользователем. В первом случае используется свойство текст. Альтернативой является использование свойства ListIndex, фиксирующего номер элемента списка, выбранного пользователем. Замечу, что часто приходится пользоваться и свойством List(i), хранящим значение i-го элемента списка.


    'Добавляет собственный пункт "Проекты" Call AddMenu("MainPanel", "&Проекты", 4, newitem) AddCustomItem newitem, "Все проекты", "Sorry" AddCustomItem newitem, "Мои проекты", "Sorry" 'Добавляем подменю Set Popupitem = AddCustomPopup(newitem, "Текущие проекты") AddCustomItem Popupitem, "Все текущие проекты", "Sorry" AddCustomItem Popupitem, "Мои текущие проекты", "Sorry" AddCustomItem newitem, "Последний проект", "Sorry"

    'Добавляет собственный пункт "Презентации" Call AddMenu("MainPanel", "&Презентации", 5, newitem) AddCustomItem newitem, "Все презентации", "Sorry" AddCustomItem newitem, "Мои презентации", "Sorry" 'Добавляем подменю Set Popupitem = AddCustomPopup(newitem, "Текущие презентации") AddCustomItem Popupitem, "Все текущие презентации", "Sorry" AddCustomItem Popupitem, "Мои текущие презентации", "Sorry" AddCustomItem newitem, "Последняя презентация", "Sorry"

    End Sub

    Листинг 3.11.

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

    Создание головного меню

    увеличить изображение
    Рис. 3.1.  Документ, имеющий собственное главное меню

    Создание головного меню

    Создадим документ, в котором:
  • Все встроенные панели отключены,
  • Главное меню содержит пять команд. Каждая команда головного меню является именем вертикально расположенного меню, выпадающего при выборе этой команды. Первые две команды главного меню (Файл, Правка) являются привычными для документов Office 2000 и содержат встроенные команды. Три другие команды главного меню (Документы, Проекты, Презентации) являются пользовательскими и содержат собственные команды, макросы для которых придется написать самостоятельно.

  • Весь инструментарий, необходимый для создания головного меню, уже создан и приведен в предыдущем разделе. Поэтому мне остается привести достаточно понятную процедуру, которая шаг за шагом создает главное меню в соответствии с приведенным планом:
    Листинг 3.11.
    (html, txt)
    Я не стал определять макросы, отвечающие за вызов каждой собственной команды меню, ограничившись стандартной заглушкой "Sorry". В заключение, взгляните на документ, в котором главное меню имеет спроектированный нами вид:
    Создание головного меню

    увеличить изображение
    Рис. 3.1.  Документ, имеющий собственное главное меню

    'Добавляет собственный пункт "Проекты" Call AddMenu("MainPanel", "&Проекты", 4, newitem) AddCustomItem newitem, "Все проекты", "Sorry" AddCustomItem newitem, "Мои проекты", "Sorry" 'Добавляем подменю Set Popupitem = AddCustomPopup(newitem, "Текущие проекты") AddCustomItem Popupitem, "Все текущие проекты", "Sorry" AddCustomItem Popupitem, "Мои текущие проекты", "Sorry" AddCustomItem newitem, "Последний проект", "Sorry"

    'Добавляет собственный пункт "Презентации" Call AddMenu("MainPanel", "&Презентации", 5, newitem) AddCustomItem newitem, "Все презентации", "Sorry" AddCustomItem newitem, "Мои презентации", "Sorry" 'Добавляем подменю Set Popupitem = AddCustomPopup(newitem, "Текущие презентации") AddCustomItem Popupitem, "Все текущие презентации", "Sorry" AddCustomItem Popupitem, "Мои текущие презентации", "Sorry" AddCustomItem newitem, "Последняя презентация", "Sorry"

    End Sub

    Листинг 3.11.

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

    Создание головного меню

    увеличить изображение
    Рис. 3.1.  Документ, имеющий собственное главное меню

    Создание интерфейса документа. Объект CommandBars

    Создание интерфейса документа - одна из важных задач, стоящих при разработке практически любых документов. Создание меню, командных кнопок и других элементов управления, вынесенных на инструментальные панели или встроенных непосредственно в документ, создание диалоговых окон (форм) - все эти задачи успешно решены еще в предыдущих версиях Office, и потому в Office 2000 немногое изменилось в этой области.
    В Office команды меню и командные кнопки располагаются на инструментальных панелях, которые составляют коллекцию CommandBars. Эти панели могут быть причалены или пристыкованы (docked) к левому, правому, верхнему или нижнему краю документа, а могут находиться и в плавающем состоянии. Каждая панель (объект CommandBar) содержит набор меню или командных кнопок - коллекцию CommandBarControls. Элементы этой коллекции (объекты CommandBarControl) в зависимости от их типа представляют отдельные меню, команды меню или командные кнопки. В Office 2000 имеется большое число встроенных панелей со встроенными элементами. Но одно из достоинств Office состоит в том, что весь этот интерфейс является настраиваемым - любую из панелей, также как и любой из элементов, расположенных на панели, можно отключить или включить в любую минуту, можно изменять элементы, показываемые на панелях. В Office 2000 это может делаться автоматически, на основе "интеллектуальных" соображений, - отключаются те команды, которые давно не использовались. Самое главное, что наряду со встроенными панелями и командами можно создавать собственные панели и команды. Это позволяет полностью отказаться от стандартного интерфейса, создав свой собственный интерфейс документа. Чаще всего, в документе используется комбинация собственных и встроенных элементов интерфейса.
    Большая часть задач по созданию собственных инструментальных панелей и элементов, располагаемых на них, обычно решается вручную без использования программирования, хотя понятно, что невозможно обойтись без написания макросов, вызываемых в ответ на нажатие командных кнопок или выбор команд меню, созданных собственноручно. Но почти все эти задачи (к сожалению, есть некоторые исключения) можно решать и программно, чем мы сейчас и займемся.

    Создание панели с кнопками - объектами класса CommandBarButton

    Напомню, класс CommandBarControl объединяет три класса объектов - CommandBarPopup, CommandBarComboBox и CommandBarButton. О двух первых из них мы уже поговорили, осталось рассмотреть самый простой класс, когда элементами панели являются кнопки. Создание панелей с кнопками - это наиболее распространенная задача, которую приходится решать при проектировании интерфейса документа. Чтобы расширить возможности стандартного документа Office 2000, добавить собственные функции, учитывающие специфику документа, достаточно написать соответствующие макросы, реализующие эти функции, связать макросы с кнопками и разместить эти кнопки на инструментальной панели.
    Когда я работаю с документом Word, то, как правило, включаю свою панель с кнопками, реализующими дополнительные функции, которые я привык использовать в своей работе. В следующем примере я построю панель, содержащую четыре такие кнопки. Макросы, связанные с первыми двумя кнопками, устраняют ошибки переключения клавиатуры, возникающие в ситуации, когда я забываю изменить раскладку клавиатуры. Я предпочитаю пользоваться собственными макросами, несмотря на то, что подобная стандартная функция включена в состав Word 2000. Мои функции мне представляются более удобными. Третий макрос производит посимвольное преобразование текста из кириллицы в латиницу. Им я часто пользуюсь при пересылке русских текстов за рубеж в случае, когда у получателя отсутствует возможность чтения кириллических текстов. Четвертый макрос служит для форматирования программного текста, копируемого из Редактора VBA, заменяя пробелы символами табуляции, изменяя стиль и выполняя другие полезные преобразования.
    Вот процедура, создающая инструментальную панель с кнопками:
    Листинг 3.15.
    (html, txt)
    Как обычно, в этой процедуре вызывается процедура добавления AddCustomButton, добавляющая кнопку на панель. Она подобна другим процедурам добавления, вот ее текст:
    Public Function AddCustomButton(panel As CommandBar, _ name As String) As CommandBarButton 'Добавляет на панель кнопку, 'возвращая объект CommandBarButton в качестве результата Dim ctrl As CommandBarButton If Not ExistControl(panel, name) Then Set ctrl = panel.Controls.Add(Type:=msoControlButton) ctrl.Caption = name End If Set AddCustomButton = panel.Controls(name) End Function

    Напомню, класс CommandBarControl объединяет три класса объектов - CommandBarPopup, CommandBarComboBox и CommandBarButton. О двух первых из них мы уже поговорили, осталось рассмотреть самый простой класс, когда элементами панели являются кнопки. Создание панелей с кнопками - это наиболее распространенная задача, которую приходится решать при проектировании интерфейса документа. Чтобы расширить возможности стандартного документа Office 2000, добавить собственные функции, учитывающие специфику документа, достаточно написать соответствующие макросы, реализующие эти функции, связать макросы с кнопками и разместить эти кнопки на инструментальной панели.
    Когда я работаю с документом Word, то, как правило, включаю свою панель с кнопками, реализующими дополнительные функции, которые я привык использовать в своей работе. В следующем примере я построю панель, содержащую четыре такие кнопки. Макросы, связанные с первыми двумя кнопками, устраняют ошибки переключения клавиатуры, возникающие в ситуации, когда я забываю изменить раскладку клавиатуры. Я предпочитаю пользоваться собственными макросами, несмотря на то, что подобная стандартная функция включена в состав Word 2000. Мои функции мне представляются более удобными. Третий макрос производит посимвольное преобразование текста из кириллицы в латиницу. Им я часто пользуюсь при пересылке русских текстов за рубеж в случае, когда у получателя отсутствует возможность чтения кириллических текстов. Четвертый макрос служит для форматирования программного текста, копируемого из Редактора VBA, заменяя пробелы символами табуляции, изменяя стиль и выполняя другие полезные преобразования.
    Вот процедура, создающая инструментальную панель с кнопками:
    Public Sub CreateButtonPanel() 'Создание панели с элементами класса CommandBarButton 'Создаем панель Dim panel As CommandBar Dim ctrl As CommandBarButton Dim Ctrl1 As CommandBarButton AddPanel ("ButtonPanel") Set panel = CommandBars("ButtonPanel") 'Добавляем на панель четыре кнопки 'Добавление кнопки ER Set ctrl = AddCustomButton(panel, "ER") 'Свойства кнопки With ctrl .OnAction = "Translate.FromEtoR" .DescriptionText = _ "Перевод в русскую раскладку клавиатуры" .TooltipText = "English -> Russian" .Style = msoButtonIconAndCaption .Tag = "English -> Russian" .FaceId = 134 End With 'Добавление кнопки RE Set ctrl = AddCustomButton(panel, "RE") 'Свойства кнопки With ctrl .OnAction = "Translate.FromRtoE" .DescriptionText = _ "Перевод в английскую раскладку клавиатуры" .TooltipText = "Russian -> English" .Style = msoButtonIconAndCaption .Tag = "Russian -> English" .FaceId = 135 End With 'Добавление кнопки RL Set ctrl = AddCustomButton(panel, "RL") 'Свойства кнопки With ctrl .OnAction = "Translate.FromRuToLat" .DescriptionText = _ "Перевод в латиницу" .TooltipText = "Кириллица -> Латиница" .Style = msoButtonIconAndCaption .Tag = "Кириллица -> Латиница" .FaceId = 137 End With 'Добавление кнопки RepNew Set ctrl = AddCustomButton(panel, "RepNew") 'Свойства кнопки With ctrl .OnAction = "Translate.RepNew" .DescriptionText = _ "Форматирование программного текста" .TooltipText = "Форматирование" .Style = msoButtonIconAndCaption .Tag = "Форматирование программного текста" .BeginGroup = True 'Поиск кнопки с подходящим рисунком Set Ctrl1 = MyFindControl("&Разметка страницы") Ctrl1.CopyFace .PasteFace End With End Sub


    Листинг 3.16.

    (html, txt)

    Вернемся к процедуре CreateButtonPanel и прокомментируем использованные в ней методы и свойства объектов класса CommandBarButton. Помимо общего для кнопок и команд меню метода OnAction, связывающего с кнопкой макрос, я использовал ряд специальных свойств и методов:

  • Свойство DescriptionText позволяет задать описание функции, выполняемой макросом.
  • Свойство TooltipText задает такое же описание, но в более краткой форме, появляющееся в виде подсказки при подведении курсора к кнопке.
  • Свойство Sryle задает внешний вид кнопки. В данном случае, когда стиль задан константой msoButtonIconAndCaption, на кнопке будет отображаться рисунок, сопровождаемый текстом.
  • Свойство Tag также задает описание элемента. Если элемент обладает свойством Tag, то его можно найти, используя значение этого свойства в качестве ключа в стандартном методе поиска FindControl. Напомню, что в этом методе в качестве ключа нельзя использовать свойство Caption .
  • Свойство FaceId позволяет задать идентификатор картинки, размещаемой на кнопке. Конечно, есть определенная трудность в том, чтобы знать какое число соответствует желаемой картинке. С этой проблемой частично можно справиться, используя методы CopyFace и PasteFace .
  • Метод CopyFace копирует в буфер картинку, размещаемую на кнопке.
  • Метод PasteFace переносит картинку из буфера на кнопку. Заметьте, на кнопку можно поместить любой рисунок в формате, допускающем сжатие. Для этого следует скопировать рисунок в буфер, а затем применить метод PasteFace.


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

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

    Создание панели с кнопками - объектами класса CommandBarButton

    увеличить изображение
    Рис. 3.2.  Документ с тремя панелями, реализующими собственный интерфейс


    Листинг 3.15.

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

    Public Function AddCustomButton(panel As CommandBar, _ name As String) As CommandBarButton 'Добавляет на панель кнопку, 'возвращая объект CommandBarButton в качестве результата Dim ctrl As CommandBarButton If Not ExistControl(panel, name) Then Set ctrl = panel.Controls.Add(Type:=msoControlButton) ctrl.Caption = name End If Set AddCustomButton = panel.Controls(name) End Function

    Листинг 3.16.

    Вернемся к процедуре CreateButtonPanel и прокомментируем использованные в ней методы и свойства объектов класса CommandBarButton. Помимо общего для кнопок и команд меню метода OnAction, связывающего с кнопкой макрос, я использовал ряд специальных свойств и методов:

  • Свойство DescriptionText позволяет задать описание функции, выполняемой макросом.
  • Свойство TooltipText задает такое же описание, но в более краткой форме, появляющееся в виде подсказки при подведении курсора к кнопке.
  • Свойство Sryle задает внешний вид кнопки. В данном случае, когда стиль задан константой msoButtonIconAndCaption, на кнопке будет отображаться рисунок, сопровождаемый текстом.
  • Свойство Tag также задает описание элемента. Если элемент обладает свойством Tag, то его можно найти, используя значение этого свойства в качестве ключа в стандартном методе поиска FindControl. Напомню, что в этом методе в качестве ключа нельзя использовать свойство Caption .
  • Свойство FaceId позволяет задать идентификатор картинки, размещаемой на кнопке. Конечно, есть определенная трудность в том, чтобы знать какое число соответствует желаемой картинке. С этой проблемой частично можно справиться, используя методы CopyFace и PasteFace .
  • Метод CopyFace копирует в буфер картинку, размещаемую на кнопке.
  • Метод PasteFace переносит картинку из буфера на кнопку. Заметьте, на кнопку можно поместить любой рисунок в формате, допускающем сжатие. Для этого следует скопировать рисунок в буфер, а затем применить метод PasteFace.



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

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

    Создание панели с кнопками - объектами класса CommandBarButton

    увеличить изображение
    Рис. 3.2.  Документ с тремя панелями, реализующими собственный интерфейс

    Свойства и методы коллекции CommandBars

    Коллекция CommandBarsобладает не только традиционными свойствами и методами, но и некоторыми специфическими свойствами. Давайте кратко познакомимся с большинством из них:
  • Property ActionControl As CommandBarControl вызывается обычно в OnAction процедуре и возвращает объект CommandBarControl, чье свойство OnAction связано с этой выполняемой процедурой. Если же такого объекта нет, то возвращается значение Nothing, что бывает при вызове этого свойства из обычной процедуры. Позволяет отключить доступ к объекту на время выполнения процедуры.
  • Property ActiveMenuBar As CommandBar возвращает объект CommandBar, представляющий активную панель меню.
  • Property DisplayKeysInTooltips As Boolean, DisplayTooltips As Boolean - если свойства имеют значения True, то на панели отображаются назначения "горячих" клавиш.
  • Property LargeButtons As Boolean- значение True позволяет включить клавиши "большого" размера.
  • Function Add([Name], [Position], [MenuBar], [Temporary]) As CommandBar. Метод Add - основной метод коллекции позволяет программным путем добавить в коллекцию новую собственную панель, дав ей имя Name. Параметр Position указывает расположение панели на экране. Его возможные значения: msoBarLeft, msoBarTop, msoBarRight, msoBarBottom указывают, куда стыковочная панель будет причалена - справа, слева, вверху или снизу экрана. Значение msoBarFloating этого параметра указывает, что панель появится в плавающем состоянии. Значение msoBarPopup указывает, что новая панель создается для контекстного меню. Булев параметр MenuBar имеет значение True, когда новая панель заменяет главную панель меню. Булев параметр Temporary позволяет сделать добавление панели временным (на момент сеанса) или постоянным.
  • Function FindControl([Type], [Id], [Tag], [Visible], [Recursive]) As CommandBarControl Позволяет найти на панелях, входящих в коллекцию, элемент, удовлетворяющий критериям поиска. Параметры, являющиеся ключами поиска, могут быть опущены, достаточно задания одного из них. Булев параметр Recursive указывает, нужно ли вести поиск на всю глубину, включая все подменю. Если критериям поиска удовлетворяет несколько элементов, то в качестве результата возвращается первый из них. Возвращается значение Nothing, если поиск был безуспешным.
  • Sub ReleaseFocus() Все панели теряют фокус.

  • В Office 2000 у этой коллекции появились новый метод, новое свойство и новое событие:
  • Function FindControls([Type], [Id], [Tag], [Visible]) As CommandBarControls. Является обобщением метода FindControl, возвращая в случае успеха коллекцию CommandBarControls, содержащую все элементы, удовлетворяющие критериям поиска.
  • Property AdaptiveMenus As Boolean. Позволяет включить или выключить свойство адаптивности встроенных меню, автоматически настраивающее их видимый состав в зависимости от частоты использования той или иной команды меню.
  • Event OnUpdate(). Событие возникает при любых изменениях командной панели объекта CommandBar.


  • Свойства и методы объекта CommandBar

    Вот основные свойства и методы самого объекта CommandBar, представляющего отдельную панель:
  • Property BuiltIn As Boolean Это булево свойство определено как для панели, так и для ее элементов, значение True указывает, что панель или элемент являются встроенными, False - определены пользователем.
  • Property Context As String - позволяет установить или проверить контекст, определяющий местоположение хранения панели, то ли непосредственно с самим документом, то ли в другом возможном месте, зависящем от приложения.
    К сожалению, это свойство не работает должным образом. Изменить его значение программным путем не удается.
  • Property Controls As CommandBarControls - можно сказать центральное свойство, возвращающее коллекцию элементов, располагаемых на панели.
  • Property Enabled As Boolean, Visible As Boolean - тоже два центральных и широко используемых свойства. Первое позволяет выключить доступ к панели, обычно временно, второе делает панель вообще невидимой и тем более недоступной.
  • Property Height As Long, Left As Long, Top As Long, Width As Long - типичные свойства графических элементов, в том числе для панели и ее элементов. Задают размеры элемента.
  • Property Index As Long - возвращает порядковый номер элемента в коллекции.
  • Property Name As String, NameLocal As String - имя панели или ее элемента, для встроенных панелей (элементов) имеют смысл два имени "родное" английское имя и локализованное.
  • Property Position As MsoBarPosition- такое же свойство, как у коллекции панелей, но применяемое к отдельно взятой панели.
  • Property Protection As MsoBarProtection - защищает панель от тех или иных действий пользователя. Значением свойства являются константы, определяющие, что конкретно нельзя делать с панелью, например, менять ее размеры или передвигать.
  • Property Type As MsoBarType- возвращает тип панели.
  • Sub Delete() - удаляет панель
  • Function FindControl([Type], [Id], [Tag], [Visible], [Recursive]) As CommandBarControl - такой же метод поиска элемента, как и описанный выше для коллекции, но поиск ограничен пределами одной панели.
  • Sub Reset() - Восстанавливает установки, принятые по умолчанию, в частности конфигурацию встроенных панелей.
  • Sub ShowPopup([x], [y]) - отображает контекстную панель в позиции, заданной курсором или параметрами X и Y, если они указаны.


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

    Анализ свойств проекта

    Перейдем теперь к рассмотрению примеров работы с объектом VBProject. Начнем с анализа терминальных свойств проектов:
    Листинг 4.1.
    (html, txt)
    Сама программа достаточно понятна. В ее первой части я в отладочном режиме печатаю число открытых проектов и в цикле по коллекции VBProjects для каждого из проектов печатаю значения его основных терминальных свойств. Обратите внимание на два обстоятельства. В двух первых строчках программы дважды описана переменная MyProject один раз как VBProject, другой - как Object. Это не случайно. Когда я пишу текст программы, я пользуюсь первым из этих объявлений, задавая раннее связывание. Тогда я получаю нужные мне подсказки о свойствах и методах этого объекта и вложенных в него объектов. Без этих подсказок жизнь программиста осложняется. Когда же я перехожу на выполнение программы, то приходится пользоваться вторым вариантом объявления, поскольку из-за недоработки в Office 2000 нормальная работа с этим объектом в документах Word невозможна, хотя в Excel работают и явные объявления типа VBProject. Заметьте также, что обращение к некоторым свойствам закомментированы. Это связано с тем, что не все заявленные новые свойства и методы реально работают в Office 2000. Следует отметить еще один небольшой, но досадный "жучок", проявляющийся в этом месте. При появлении подсказки ряд существующих свойств и методов не показывается, хотя они фактически работают, например, не показывается свойство Protection и Mode.
    Списки свойств и методов объекта VBProject, но и не только этого объекта, просматриваемые в трех разных местах в справочной системе, в окне просмотра (браузере объектов) и при появлении подсказки в момент программирования, к сожалению, могут не совпадать.
    Во второй части этой процедуры приводятся некоторые свойства коллекции AddIns - GUID и описание каждого элемента.
    Есть смысл привести результаты тестовой печати при работе этой процедуры. Я выполнял ее в тот момент, когда были открыты два документа Word, один из них текст этой лекции, второй тестовый документ, содержащий тексты выполняемых процедур. Вот результаты печати:

    Листинг 4.2.

    (html, txt)

    Как и положено, при двух открытых документах коллекция VBProjects содержит три проекта. Проекту моего тестового документа в свое время я дал имя (DocOneProject) и указал для него описание. О коллекции AddIns я уже говорил, но, возможно, Вам будет интересно взглянуть на их полное описание.

    Документы и проекты с объектной точки зрения

    Объект Document имеет свойство VBProject, возвращающее объект, задающий программный проект данного документа. С другой стороны, объект VBProject является элементом коллекции VBProjects, содержащей проекты всех открытых документов. Коллекция VBProjects, в свою очередь, вложена в объект VBE. Поэтому давайте поднимемся на несколько этажей выше проекта. Я напомню, что корневым объектом, содержащим все объекты VBA, является объект VBE (Visual Basic Environment или Editor). У этого объекта есть только свойства, позволяющие получить доступ к объектам VBA, лежащим на нижних уровнях иерархии. В справочной системе приведена следующая объектная модель VBE:
    Документы и проекты с объектной точки зрения

    Рис. 4.1.  Модель объекта VBE
    Эта модель далеко не полная. Она отражает только коллекции, входящие на верхнем уровне в объект VBE. Рассмотрим коротко эти коллекции:
  • Конечно, основной является коллекция проектов - VBProjects. Эту коллекцию составляют все проекты открытых документов приложения. Заметьте, если одновременно открыто N документов Word, то коллекция VBProjects будет содержать не менее чем N+1 элемент, по одному проекту на документ плюс общий для всех проект Normal. Заметьте также, проекты разных приложений, например, Excel и Word входят, к сожалению, в разные коллекции, что затрудняет программную работу в проекте документа Word с проектом Excel. Совместная работа с разными проектами внутри одного приложения не вызывает особых трудностей. Достаточно просто иметь общие переменные, вызывать общие процедуры.
  • О том, как создать собственные надстройки - AddIn, разговор пойдет в последующих лекциях. Сейчас же замечу, что коллекция надстроек, доступных в приложении Word 2000, не пуста. В настоящий момент у меня на компьютере доступны восемь таких надстроек, среди них WinApiViewer, AddIn для построения строк, задающих SQL-операторы, AddIn, реализующий множественный импорт- экспорт объектов за одну операцию и другие.
  • Коллекция окон Windows содержит объекты, задающие окна в Редакторе Visual Basic. Восемь окон входит в эту коллекцию, - окно проекта и окно кода, окно отладки, окно локальных переменных и другие.
  • Коллекция CodePanes содержит подокна, задающие активные окна кода, обычно таких открытых окон в процессе работы одно, но может быть и несколько ведется параллельная отладка нескольких проектов.
  • Коллекция CommandBars содержит, как правило, несколько десятков элементов, задающих инструментальные панели в среде Редактора VBA.


  • Объект Document имеет свойство VBProject, возвращающее объект, задающий программный проект данного документа. С другой стороны, объект VBProject является элементом коллекции VBProjects, содержащей проекты всех открытых документов. Коллекция VBProjects, в свою очередь, вложена в объект VBE. Поэтому давайте поднимемся на несколько этажей выше проекта. Я напомню, что корневым объектом, содержащим все объекты VBA, является объект VBE (Visual Basic Environment или Editor). У этого объекта есть только свойства, позволяющие получить доступ к объектам VBA, лежащим на нижних уровнях иерархии. В справочной системе приведена следующая объектная модель VBE:
    Документы и проекты с объектной точки зрения

    Рис. 4.1.  Модель объекта VBE
    Эта модель далеко не полная. Она отражает только коллекции, входящие на верхнем уровне в объект VBE. Рассмотрим коротко эти коллекции:
  • Конечно, основной является коллекция проектов - VBProjects. Эту коллекцию составляют все проекты открытых документов приложения. Заметьте, если одновременно открыто N документов Word, то коллекция VBProjects будет содержать не менее чем N+1 элемент, по одному проекту на документ плюс общий для всех проект Normal. Заметьте также, проекты разных приложений, например, Excel и Word входят, к сожалению, в разные коллекции, что затрудняет программную работу в проекте документа Word с проектом Excel. Совместная работа с разными проектами внутри одного приложения не вызывает особых трудностей. Достаточно просто иметь общие переменные, вызывать общие процедуры.
  • О том, как создать собственные надстройки - AddIn, разговор пойдет в последующих лекциях. Сейчас же замечу, что коллекция надстроек, доступных в приложении Word 2000, не пуста. В настоящий момент у меня на компьютере доступны восемь таких надстроек, среди них WinApiViewer, AddIn для построения строк, задающих SQL-операторы, AddIn, реализующий множественный импорт- экспорт объектов за одну операцию и другие.
  • Коллекция окон Windows содержит объекты, задающие окна в Редакторе Visual Basic. Восемь окон входит в эту коллекцию, - окно проекта и окно кода, окно отладки, окно локальных переменных и другие.
  • Коллекция CodePanes содержит подокна, задающие активные окна кода, обычно таких открытых окон в процессе работы одно, но может быть и несколько ведется параллельная отладка нескольких проектов.
  • Коллекция CommandBars содержит, как правило, несколько десятков элементов, задающих инструментальные панели в среде Редактора VBA.

  • Помимо пяти коллекций в объект VBE вложено большое число других объектов, не являющихся коллекциями. Отмечу среди них лишь некоторые объекты.
    Объект Events напоминает коллекцию. Каждое из его свойств возвращает объект, тип которого совпадает с именем свойства. Возвращаемые объекты обладают определенным набором событий, которые могут быть подключены в создаваемых надстройках Addins, позволяя реагировать на те или иные действия. Большинство событий связано с добавлением, удалением, переименованием объектов в коллекциях. Так свойство ReferencesEvents объекта Events возвращает объект ReferencesEvents, обладающий двумя событиями - ItemAdded, ItemRemoved, возникающими при добавлении или удалении ссылок - элементов коллекции References.
    Три Active-свойства объекта VBE возвращают соответствующие активные объекты - ActiveVBProject, ActiveWindow, ActiveCodePane. Похожим является свойство SelectedVBComponent, возвращающее выбранную компоненту проекта


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

    Объект Events напоминает коллекцию. Каждое из его свойств возвращает объект, тип которого совпадает с именем свойства. Возвращаемые объекты обладают определенным набором событий, которые могут быть подключены в создаваемых надстройках Addins, позволяя реагировать на те или иные действия. Большинство событий связано с добавлением, удалением, переименованием объектов в коллекциях. Так свойство ReferencesEvents объекта Events возвращает объект ReferencesEvents, обладающий двумя событиями - ItemAdded, ItemRemoved, возникающими при добавлении или удалении ссылок - элементов коллекции References.

    Три Active-свойства объекта VBE возвращают соответствующие активные объекты - ActiveVBProject, ActiveWindow, ActiveCodePane. Похожим является свойство SelectedVBComponent, возвращающее выбранную компоненту проекта

    Объект VBProject и коллекция VBProjects

    В Office 2000 возможности работы с программными проектами документов существенно расширились. У объекта VBProject и коллекции VBProjects в Office 2000 появилось большое число новых свойств и методов. Замечу, что определенную сложность в изучение этого материала вносит то обстоятельство, что в справочной системе справки по многим объектам, связанным с проектами, отсутствуют или, что еще хуже, содержат неполные сведения, не отражающие действительной картины. Так было и в Office 97, такая же ситуация имеет место и в Office 2000.
    Скажем несколько слов о коллекции VBProjects. В Office 97 у этой коллекции имелись три простых свойства Count, Parent, VBE и единственный метод - Item. У нее не было методов Add и Delete, так как проекты были прочно связаны со своими документами и удалялись и появлялись одновременно со своими документами. В Office 2000 у этой коллеции появились три метода для добавления проектов в коллекцию Add, AddFromFile, AddFromTemplate, соответственно появился и метод Remove для удаления элементов коллекции. Помимо этого, коллекция имеет методы SaveAs и FileName для сохранения проектов, появились также и новые свойства, в частности свойство StartProject.
    С этими новыми свойствами и методами еще предстоит разобраться. Пока они у меня не работают. Я не провел еще всех необходимых исследований и посему не готов дать точный ответ даже на такой простой вопрос, как добавить проект в коллекцию VBProjects?
    Имея коллекцию VBProjects, добраться до отдельного проекта нетрудно, делается это обычным для коллекций способом, Можно, конечно, использовать метод Item, но, проще всего, указать индекс элемента в коллекции, при этом роль индекса может играть и имя проекта. Так что спуститься по иерархии сверху вниз нетрудно: VBE.VBProjects(1) дает ссылку на первый проект коллекции. Также просто и от отдельного проекта перейти вверх по иерархии. Объект VBProject имеет свойство Collection, позволяющее получить коллекцию VBProjects, а его свойство VBE позволяет подняться еще выше по иерархии и добраться до корневого объекта VBE; от корня можно уже спуститься в любую заданную точку. Итак, разобрав, как связан объект VBProject с объектами, вышестоящими в иерархии, давайте более подробно остановимся на его свойствах и методах.
    В Office 97 объект класса VBProject имел только свойства и не имел методов. О двух свойствах этого объекта Collection и VBE я уже упомянул, они позволяют подняться по иерархии. Свойства Description и Protection позволяют получить описание проекта и определить, защищен ли он. Свойства HelpFile и HelpContextId позволяют указать справочную систему по проекту, если таковая существует. Наиболее важными, пожалуй, свойствами являются свойства VBComponents и References. Свойство VBComponents возвращает коллекцию компонент проекта: модули, классы и формы, входящие в проект. Имея эту коллекцию, можно перейти к соответствующей компоненте и программно работать с ней. Свойство References позволяет получить доступ к коллекции ссылок на элементы, доступные из данного проекта. Элементы этой коллекции соответствуют ссылкам, отображаемым в пункте References меню Tools в среде редактора VB.
    В Office 2000 объект VBProject получил несколько новых свойств, например, свойства: CompatibleOleServer, IsDirty, Type. Первое из них возвращает или устанавливает строку, содержащую совместимый сервер Автоматизации для проекта. Булево свойство IsDirty, как обычно для свойства с таким именем указывает на внесение изменений в проект с момента последнего его сохранения. Свойство Type задает тип проекта, указывающий на то, что проект может относиться, например, к ActiveXDLL или ActiveXControl. Объект VBProject теперь имеет и методы. Метод MakeCompiledFile записывает проект в виде библиотеки DLL, имя которой задается новым свойством BuildFileName. Метод SaveAs позволяет сохранить проект в указанном месте.
    Изменения в составе свойств и методов объектов VBProject и VBProjects симптоматичны. Они отражают тенденцию к возрастанию потребностей офисных программистов при работе с программными проектами документов.

    Объекты программного проекта. Программирование на лету

    Всюду, где только можно, я не уставал повторять, как заклинание, что для офисного программиста целью работы является создание документа, частью которого является программный проект, неразрывно связанный с документом. Более того, в серьезных разработках речь всегда идет о создании системы документов, а, следовательно, и о системе программных проектов, связанных с этими документами, так и с помощью ссылок непосредственно друг с другом. Программные проекты в такой системе могут иметь общий пул памяти, общие процедуры, вызываемые из любого проекта. Я хочу рассмотреть отношения между документами и проектами с объектной точки зрения, рассмотреть основной объект VBProject, задающий проект, его компоненты, его связь с объектом Document и коллекцией VBProjects, хочу рассмотреть вопросы программной работы с этими объектами. Всюду в этом тексте, где речь идет о документах, я буду рассматривать документы Word, а, следовательно, с ними связывать программные проекты. Но нужно понимать, что объектная модель проектов практически одна и та же для всех документов Office 2000. Объекты, о которых пойдет речь, являются общими и находятся в общей библиотеке VBIDE.
    В отличие от других документов Office документы Word обладают той особенностью, что с каждым из них связаны два проекта. Один - Normal - стандартный проект, общий для всех документов, а второй - отражает специфику документа. Этот второй проект обычно и называют проектом данного документа, и именно о нем пойдет основной наш разговор. Но прежде несколько слов о проекте Normal. Все документы Word открываются по умолчанию на основе шаблона Normal, частью этого шаблона и является, связанный с шаблоном проект Normal. Все макросы, хранимые в проекте Normal, являются доступными для любого из документов Word. Именно сюда может поместить программист все макросы, стандартные модули или модули классов, которые, по его мнению, будут использоваться всеми документами Word на данном компьютере. Нужно, однако, понимать, что при размещении макроса в проекте Normal, речь должна идти действительно только об общезначимых программных инструментах. В остальных случаях все программные компоненты должны быть связаны с проектом конкретного документа или включены в специальную надстройку - AddIn. Замечу, кстати, что иногда помещение программных компонент, чаще всего макросов, в проект Normal происходит по недоразумению, поскольку по умолчанию местом размещения создаваемых MacroRecorder макросов является именно проект Normal, а не проект данного документа. Так что будьте внимательны в таких ситуациях. Но давайте перейдем к рассмотрению объектной модели проекта. Но вначале несколько слов о том, как связаны на объектном уровне документы с проектами и как связаны между собой проекты разных документов.

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

    Теперь, когда мы умеем программно включать в системе проектов ссылку на новый программный проект, осталось рассмотреть, как обращаться к элементам этого проекта, его общедоступным переменным, объектам и процедурам. Для обращения к элементу проекта, на который есть ссылка, необходимо указать его полное имя, включающее имя проекта, имя модуля и собственное имя элемента. В моем примере я буду вызывать процедуру с одним и тем же собственным именем TestPrint, которая есть в обоих подключаемых проектах DocTwo и DocThree. Чтобы вызвать процедуру того проекта, который подключен в текущий момент, очевидно, необходимо организовать разбор случаев. Это нетрудно сделать, зная имя (имена) подключенных проектов. Здесь есть правда некоторый подводный камень. Дело в том, что в соответствующем операторе Case только имя одного элемента будет определено, того элемента, который принадлежит подключенному проекту, имена вызываемых элементов из других, неподключенных проектов будут неопределенными, более того, считаться необъявленными, что немедленно приведет к обычной синтаксической ошибке. Из этой ситуации есть простой выход. Но прежде, чем рассказать о нем, давайте взглянем на текст соответствующей процедуры, занимающейся разбором случаев:
    Public Sub CallProcFromProject() 'Вызов процедуры подключаемого проекта 'Разбор случаев, какой проект подключен If NoP = "DocTwo" Then Proc2 ElseIf NoP = "DocThree" Then Proc3 End If End Sub
    Public Sub Proc2() 'Вызов процедуры проекта 2 DocTwo.DocTwoModule.TestPrint End Sub Public Sub Proc3() 'Вызов процедуры проекта 2 DocThree.DocThreeModule.TestPrint End Sub
    Листинг 4.11.
    (html, txt)
    В нашей простой ситуации разбором занимается простейший оператор IF, который в зависимости от имени проекта вызывает процедуру TestPrint того или иного проекта. Но заметьте, я не стал в текст этой процедуры вставлять явный вызов процедуры проекта, заменив его вызовом внутренней процедуры, являющейся оберткой настоящего вызова. Таким образом, процедура CallProcFromProject является синтаксически корректной, более того она вызывает синтаксически корректную процедуру, если, конечно, разбор работает корректно. Синтаксически некорректные процедуры не вызываются, их существование в проекте не мешает его нормальной работе. Это, конечно, "уловка", но позволяющая достигнуть заданной цели. Хочу обратить внимание на еще один аспект, связанный с работой этой процедуры. Вы, возможно, заметили, эта процедура вызывается в теле процедуры CreateRef, сразу же после создания ссылки на подключаемый проект. Давайте еще раз вернемся к рассмотрению метода AddFromFile, создающего ссылку в коллекции References. Конечно, главным итогом работы этого метода будет не только создание ссылки, но и явное подключение нового документа с его проектом. Это означает, что, если соответствующий документ не был открыт к моменту создания ссылки, то он автоматически откроется. Необходимо иметь в виду еще одно следствие этого процесса, после завершения процедуры, создающей ссылки в связи с подключением нового проекта, происходит сброс выполнения текущего проекта. Это означает, в частности, что все глобальные переменные будут обнулены, и, следовательно, будут потеряны значения переменных, хранящих имена подключаемых проектов. Именно поэтому вызов процедуры CallProcFromProject делается в той же процедуре, в которой новый проект подключается. В противном случае необходимо было повторно запрашивать у пользователя имена подключаемых проектов.
    Прежде, чем поставить точку на описании решения рассматриваемой задачи, хочу обсудить еще один важный ее аспект.
    Будет ли задача успешно решаться, если подключаемые проекты закрыты для просмотра и защищены паролем?
    Дело в том, что программно снять защиту с проектов нельзя, поскольку свойство Protection является свойством "только для чтения" и позволяет лишь узнать, закрыт проект или нет. К счастью, программный вызов элементов проекта возможен и для закрытых проектов. Таким образом, с закрытыми проектами можно работать точно также как и с открытыми.
    В заключение хочу высказать несколько общих замечаний. Прежде всего, следует отметить, что программное подключение и отключение ссылок может быть полезным не только при работе с подключаемыми программными проектами. Список возможных Com-объектов, подключаемых к документу через меню References велик, и зачастую возникает необходимость в программном их подключении.
    Хочу отметить также, что есть два альтернативных варианта, когда следует создавать систему программных проектов, связанных ссылками. Для первого варианта характерна ситуация, когда из "разных точек общего проекта системы необходимо вызывать одни и те же процедуры", когда есть общий пул данных, с которым работают все проекты, общие процедуры обработки этих данных. В этом варианте легко спроектировать структуру системы проектов в виде дерева, в корне которого будет проект, содержащий общие данные и процедуры и на который будут ссылаться все остальные проекты. В такой ситуации все ссылки известны на этапе проектирования, их можно установить вручную, и никаких проблем нет. Сейчас же рассматривался другой вариант, когда "из одной точки проекта необходимо вызывать разные процедуры". В этом случае программное подключение проектов может быть предпочтительнее.
    Следует также сказать, что реальная проблема С. Шершнева оказалась сложнее описанной и не укладывается ни в один из двух рассматриваемых вариантов. В его ситуации нельзя было заранее написать процедуру CallProcFromProject, поскольку разбираемые в ней случаи появлялись динамически. Надеюсь, понятно, что выходом в такой ситуации является корректировка кода этой процедуры программно, "на лету" в тот момент, когда появляется новый "случай", новый клиент в системе, создаваемой С.Шершневым. Но, как было показано, сделать это возможно и не так уж и сложно.

    Теперь, когда мы умеем программно включать в системе проектов ссылку на новый программный проект, осталось рассмотреть, как обращаться к элементам этого проекта, его общедоступным переменным, объектам и процедурам. Для обращения к элементу проекта, на который есть ссылка, необходимо указать его полное имя, включающее имя проекта, имя модуля и собственное имя элемента. В моем примере я буду вызывать процедуру с одним и тем же собственным именем TestPrint, которая есть в обоих подключаемых проектах DocTwo и DocThree. Чтобы вызвать процедуру того проекта, который подключен в текущий момент, очевидно, необходимо организовать разбор случаев. Это нетрудно сделать, зная имя (имена) подключенных проектов. Здесь есть правда некоторый подводный камень. Дело в том, что в соответствующем операторе Case только имя одного элемента будет определено, того элемента, который принадлежит подключенному проекту, имена вызываемых элементов из других, неподключенных проектов будут неопределенными, более того, считаться необъявленными, что немедленно приведет к обычной синтаксической ошибке. Из этой ситуации есть простой выход. Но прежде, чем рассказать о нем, давайте взглянем на текст соответствующей процедуры, занимающейся разбором случаев:
    Public Sub CallProcFromProject() 'Вызов процедуры подключаемого проекта 'Разбор случаев, какой проект подключен If NoP = "DocTwo" Then Proc2 ElseIf NoP = "DocThree" Then Proc3 End If End Sub
    Public Sub Proc2() 'Вызов процедуры проекта 2 DocTwo.DocTwoModule.TestPrint End Sub Public Sub Proc3() 'Вызов процедуры проекта 2 DocThree.DocThreeModule.TestPrint End Sub
    Листинг 4.11.
    В нашей простой ситуации разбором занимается простейший оператор IF, который в зависимости от имени проекта вызывает процедуру TestPrint того или иного проекта. Но заметьте, я не стал в текст этой процедуры вставлять явный вызов процедуры проекта, заменив его вызовом внутренней процедуры, являющейся оберткой настоящего вызова. Таким образом, процедура CallProcFromProject является синтаксически корректной, более того она вызывает синтаксически корректную процедуру, если, конечно, разбор работает корректно. Синтаксически некорректные процедуры не вызываются, их существование в проекте не мешает его нормальной работе. Это, конечно, "уловка", но позволяющая достигнуть заданной цели. Хочу обратить внимание на еще один аспект, связанный с работой этой процедуры. Вы, возможно, заметили, эта процедура вызывается в теле процедуры CreateRef, сразу же после создания ссылки на подключаемый проект. Давайте еще раз вернемся к рассмотрению метода AddFromFile, создающего ссылку в коллекции References. Конечно, главным итогом работы этого метода будет не только создание ссылки, но и явное подключение нового документа с его проектом. Это означает, что, если соответствующий документ не был открыт к моменту создания ссылки, то он автоматически откроется. Необходимо иметь в виду еще одно следствие этого процесса, после завершения процедуры, создающей ссылки в связи с подключением нового проекта, происходит сброс выполнения текущего проекта. Это означает, в частности, что все глобальные переменные будут обнулены, и, следовательно, будут потеряны значения переменных, хранящих имена подключаемых проектов. Именно поэтому вызов процедуры CallProcFromProject делается в той же процедуре, в которой новый проект подключается. В противном случае необходимо было повторно запрашивать у пользователя имена подключаемых проектов.
    Прежде, чем поставить точку на описании решения рассматриваемой задачи, хочу обсудить еще один важный ее аспект.
    Будет ли задача успешно решаться, если подключаемые проекты закрыты для просмотра и защищены паролем?
    Дело в том, что программно снять защиту с проектов нельзя, поскольку свойство Protection является свойством "только для чтения" и позволяет лишь узнать, закрыт проект или нет. К счастью, программный вызов элементов проекта возможен и для закрытых проектов. Таким образом, с закрытыми проектами можно работать точно также как и с открытыми.
    В заключение хочу высказать несколько общих замечаний. Прежде всего, следует отметить, что программное подключение и отключение ссылок может быть полезным не только при работе с подключаемыми программными проектами. Список возможных Com-объектов, подключаемых к документу через меню References велик, и зачастую возникает необходимость в программном их подключении.
    Хочу отметить также, что есть два альтернативных варианта, когда следует создавать систему программных проектов, связанных ссылками. Для первого варианта характерна ситуация, когда из "разных точек общего проекта системы необходимо вызывать одни и те же процедуры", когда есть общий пул данных, с которым работают все проекты, общие процедуры обработки этих данных. В этом варианте легко спроектировать структуру системы проектов в виде дерева, в корне которого будет проект, содержащий общие данные и процедуры и на который будут ссылаться все остальные проекты. В такой ситуации все ссылки известны на этапе проектирования, их можно установить вручную, и никаких проблем нет. Сейчас же рассматривался другой вариант, когда "из одной точки проекта необходимо вызывать разные процедуры". В этом случае программное подключение проектов может быть предпочтительнее.
    Следует также сказать, что реальная проблема С. Шершнева оказалась сложнее описанной и не укладывается ни в один из двух рассматриваемых вариантов. В его ситуации нельзя было заранее написать процедуру CallProcFromProject, поскольку разбираемые в ней случаи появлялись динамически. Надеюсь, понятно, что выходом в такой ситуации является корректировка кода этой процедуры программно, "на лету" в тот момент, когда появляется новый "случай", новый клиент в системе, создаваемой С.Шершневым. Но, как было показано, сделать это возможно и не так уж и сложно.

    Анализ терминальных свойств проектов коллекции

    Public Sub WorkWithProjects() ' Анализ терминальных свойств проектов коллекции VBProjects 'Dim MyProject As VBProject Dim MyProject As Object 'Dim MyAddIn As AddIn Dim MyAddIn As Object 'Анализ свойств проектов Debug.Print "Число проектов в коллекции - ", VBE.VBProjects.Count For Each MyProject In VBE.VBProjects With MyProject Debug.Print "Имя Файла - ", .FileName Debug.Print "Имя проекта", .Name Debug.Print "Тип проекта", .Type Debug.Print "Описание проекта", .Description Debug.Print "Статус защиты", .Protection Debug.Print "Статус проекта", .Mode 'Debug.Print "Изменения Вносились? ", .IsDirty Debug.Print "Имя Dll", .BuildFileName 'Debug.Print "Начальный статус", .StartMode End With Next MyProject ' Анализ коллекции AddIns Debug.Print "Число AddIn в коллекции = ", VBE.AddIns.Count For Each MyAddIn In VBE.AddIns Debug.Print "GUID AddIn = ", MyAddIn.GUID Debug.Print "Описание AddIn - ", MyAddIn.Description Next MyAddIn End Sub
    Листинг 4.1.
    Закрыть окно

    с именем NoP Dim MyRef

    Public Sub RemoveRef() 'Удаление ссылки на проект с именем NoP Dim MyRef As Object Dim NameOfProject As String Dim NameOfFile As String 'Выбор удаляемого проекта Call ChooseProject(NameOfFile, NameOfProject) 'Удаление ссылки If ExistRef(NameOfProject, MyRef) Then ActiveDocument.VBProject.References.Remove MyRef End If End Sub
    Листинг 4.10.
    Закрыть окно

    Разбор случаев, какой проект подключен

    Public Sub CallProcFromProject() 'Вызов процедуры подключаемого проекта ' Разбор случаев, какой проект подключен If NoP = "DocTwo" Then Proc2 ElseIf NoP = "DocThree" Then Proc3 End If End Sub
    Public Sub Proc2() 'Вызов процедуры проекта 2 DocTwo.DocTwoModule.TestPrint End Sub Public Sub Proc3() 'Вызов процедуры проекта 2 DocThree.DocThreeModule.TestPrint End Sub
    Листинг 4.11.
    Закрыть окно

    doc Имя проекта Project Тип

    Число проектов в коллекции - 3 Имя Файла - E:\O2000\DS2000\Ch8\Ch8. doc Имя проекта Project Тип проекта 100 Описание проекта Статус защиты 0 Статус проекта 0 Имя Dll - E:\O2000\DS2000\Ch8\Ch8.DLL Имя Файла - E:\O2000\Book2\Ch1\Normal Имя проекта Normal Тип проекта 100 Описание проекта Статус защиты 0 Статус проекта 0 Имя Dll - E:\O2000\Book2\Ch1\Normal.DLL Имя Файла - E:\O2000\Book2\CD\Ch1\DocOne.doc Имя проекта DocOneProject Тип проекта 100 Описание проекта - Этот проект содержит примеры главы 2 Статус защиты 0 Статус проекта 0 Имя Dll - ProjectDll
    Число AddIn в коллекции = 8 GUID AddIn = {7A588DE1-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the VBA String Editor to quickly and accurately build strings for SQL statements or long scripts to embed in VBA code. GUID AddIn = {6961B1FB-3ECD-11D2-B81C-0060089A6839} Описание AddIn - Use the Package and Deployment Wizard to package applications for installation and deployment. GUID AddIn = {0D87BB44-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the Error Handler Add-in to automate creating standardized error handler code with input dialogs that capture basic information and insert standardized error handling code using a customizable template. GUID AddIn = {0D87BB42-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the Code Commenter Add-in to create well-commented code by automatically adding comments and headers to procedures using customizable templates. GUID AddIn = {0D87BADE-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the Multi-Code Import/Export Add-in to import or export multiple objects in a single operation. GUID AddIn = {0D87BB14-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the WinAPI Viewer utility to view and copy Win32 API Constants, Declares, and Types. GUID AddIn = {0D87BAE8-D5FF-11D2-A1F8-00A0C9A70018} Описание AddIn - Use the VBA Source Code Control Add-in to take advantage of the Microsoft Visual SourceSafe source code control tool from within the Visual Basic for Applications 6.0 IDE. GUID AddIn = {31E27D66-596E-11D2-B826-0060089A6839} Описание AddIn - Use the Code Librarian to reuse and share code in a centralized database.
    Листинг 4.2.
    Закрыть окно

    Dim MyProject As VBProject Dim

    Public Sub WorkWithVBProject() 'Компоненты и ссылки проекта ' Dim MyProject As VBProject Dim MyProject As Object 'Dim MyComp As VBComponent Dim MyComp As Object 'Dim MyRef As Reference Dim MyRef As Object Set MyProject = ActiveDocument.VBProject With MyProject 'Печать имени и типа для каждой существующей компоненты проекта Debug.Print "Число компонент проекта - ", .VBComponents.Count For Each MyComp In .VBComponents Debug.Print "Имя компоненты - ", MyComp.Name, "Тип - ", MyComp.Type If MyComp.Name = "NewMacros" Then MyComp.Name = "DocOneMacros" Next MyComp 'Добавление формы - новой компоненты проекта .VBComponents.Add vbext_ct_MSForm
    'Печать имени и типа для каждой существующей компоненты проекта Debug.Print "Число компонент проекта - ", .VBComponents.Count For Each MyComp In .VBComponents Debug.Print "Имя компоненты - ", MyComp.Name, "Тип - ", MyComp.Type If MyComp.Name = "NewMacros" Then MyComp.Name = "DocOneMacros" Next MyComp
    'Печать имени и типа для каждой существующей ссылки проекта For Each MyRef In .References Debug.Print "Имя ссылки -", MyRef.Name, "Тип -", MyRef.Type Next MyRef
    End With End Sub
    Листинг 4.3.
    Закрыть окно

    Число компонент проекта

    Число компонент проекта - 5 Имя компоненты - ThisDocument Тип - 100 Имя компоненты - Examples Тип - 1 Имя компоненты - DocOneMacros Тип - 1 Имя компоненты - Examples1 Тип - 1 Имя компоненты - EventsOfApp Тип - 2 Число компонент проекта - 6 Имя компоненты - ThisDocument Тип - 100 Имя компоненты - Examples Тип - 1 Имя компоненты - DocOneMacros Тип - 1 Имя компоненты - Examples1 Тип - 1 Имя компоненты - EventsOfApp Тип - 2 Имя компоненты - UserForm1 Тип - 3 Имя ссылки - VBA Тип - 0 Имя ссылки - Word Тип - 0 Имя ссылки - stdole Тип - 0 Имя ссылки - Normal Тип - 1 Имя ссылки - Office Тип - 0 Имя ссылки - MSForms Тип - 0 Имя ссылки - VBIDE Тип - 0 Имя ссылки - EventSystemLib Тип - 0
    Листинг 4.4.
    Закрыть окно

    Создание переменной

    Private Sub Document_New() Const MyPath = "e:\O2000\Book2\Cd\Ch1\" Const Lin1 = "OpenDoc" Dim Beg As Long 'Создание переменной - хранителя информации в новом документе With ActiveDocument.Variables If Not ExistVar("CounterDoc") Then 'Добавляем переменную .Add Name:="CounterDoc", Value:= 0 End If End With With ActiveDocument.VBProject.VBComponents("ThisDocument").CodeModule 'Создание процедуры - события в новом документе Call .CreateEventProc("Open", "Document") 'Определение точки вставки в процедуру 'Beg = .ProcStartLine("Document_Open", 0) Beg = .ProcBodyLine("Document_Open", 0) 'Вставка текста в процедуру Call .InsertLines(Beg + 1, Lin1) End With 'Добавление модуля в проект нового документа ActiveDocument.VBProject.VBComponents.Add (vbext_ct_StdModule) 'Переименование модуля ActiveDocument.VBProject.VBComponents("Module1").Name = "AddedModule" With ActiveDocument.VBProject.VBComponents("AddedModule").CodeModule
    'вставка текста процедуры из файла .AddFromFile (MyPath & "AddingModule.bas") End With
    End Sub
    Листинг 4.5.
    Закрыть окно

    Name As String) As Boolean

    Public Function ExistVar( Name As String) As Boolean 'Определяет наличие переменной Name в коллекции Variables Dim MyVar As Variable ExistVar = False For Each MyVar In ActiveDocument.Variables If MyVar.Name = Name Then ExistVar = True: Exit For End If Next MyVar End Function
    Public Sub OpenDoc() 'Использование счетчика Counter для подсчета числа открытий документа Dim myLocal As Integer 'локальная переменная получает значение счетчика With ActiveDocument If ExistVar("CounterDoc") Then myLocal = .Variables("CounterDoc") MsgBox "Число открытий документа " & .Name & vbCrLf & _ myLocal, vbExclamation, "Число открытий документа!" 'Увеличиваем и сохраняем счетчик myLocal = myLocal + 1 .Variables("CounterDoc") = myLocal Else MsgBox "У документа " & .Name _ & " нет счетчика числа открытий", vbExclamation, "Число открытий документа!" End If End With End Sub
    Листинг 4.6.
    Закрыть окно

    NoF As String, NoP As

    Public Sub ChooseProject( NoF As String, NoP As String) Const Msg = "Введите имя файла, хранящего документ и его программный проект!" NoF = InputBox(Msg, "Projects", "DocTwo.doc") Const Msg1 = "Введите имя проекта, хранящегося в документе!" NoP = InputBox(Msg1, "Projects", "DocTwo") End Sub
    Листинг 4.7.
    Закрыть окно

    с именем NoF Dim MyPath

    Public Sub CreateRef() 'Создание ссылки на проект с именем NoP, хранящийся в файле с именем NoF Dim MyPath As String Dim MyRef As Object Dim NameOfProject As String Dim NameOfFile As String
    'Выбор добавляемого проекта Call ChooseProject(NameOfFile, NameOfProject)
    'Запоминание глобальных переменных программы NoF = NameOfFile NoP = NameOfProject With ActiveDocument MyPath = .Path 'Вставка ссылки If Not ExistRef(NameOfProject, MyRef) Then .VBProject.References.AddFromFile MyPath & "\" & NameOfFile End If CallProcFromProject Debug.Print "Имя файла -", NoF, "Имя проекта -", NoP End With End Sub
    Листинг 4.8.
    Закрыть окно

    Name As String, Refery As

    Public Function ExistRef( Name As String, Refery As Object) As Boolean 'Определяет наличие ссылки с именем Name в коллекции References 'Возвращает ссылку при ее обнаружении Dim MyRef As Object Set Refery = Nothing ExistRef = False For Each MyRef In ActiveDocument.VBProject.References If MyRef.Name = Name Then Set Refery = MyRef ExistRef = True Exit For End If Next MyRef End Function
    Листинг 4.9.
    Закрыть окно

    Программирование на лету

    Сейчас я хочу рассмотреть одну важную тему при работе с программными проектами. Иногда работа программиста с проектом заключается в том, что он программно должен изменить сам текст проекта, добавляя новые модули, новые процедуры и обработчики событий, корректируя текст модуля и его отдельных процедур, создавая или меняя свой проект, как говорят, "на лету". Я уже говорил о том, что, благодаря коллекции VBComponents, можно добраться до каждого модуля проекта, а благодаря свойству CodeModule, получить код модуля. Свойством CodeModule обладает объект VBComponents("NameofModule"), задающий модуль проекта с именем NameOfModule. При вызове этого свойства возвращается объект CodeModule, определяющий код модуля. У этого объекта много важных и полезных свойств и методов, необходимых при программной работе с кодом проекта. Благодаря таким свойствам как CountOfLines, CountOfDeclarationsLines, ProcCountLines, можно узнать число строк в модуле, число строк в разделе объявлений, число строк в процедуре модуля с заданным при вызове именем. Работая со свойством Members, можно получить полную информацию обо всех элементах модуля. Свойства ProcBodyLine и ProcStartLine возвращают номер строки, с которой начинается процедура или предшествующей ей строки. Свойство Lines возвращает заданное число строк процедуры, свойство ProcOfLine возвращает имя процедуры, содержащей заданную при вызове строку. Если свойства объекта CodeModule позволяют проанализировать состав модуля и добраться до каждой из его процедур, то методы объекта позволяют вставлять, заменять и удалять строки кода, так что можно "на лету" провести коррекцию процедуры, удалить или добавить новую процедуру и/или объявление переменной.
    Методы AddFromFile и AddFromString позволяют добавить в модуль текст, сохраненный либо в файле, либо непосредственно в строке. Первый метод, как правило, используется для введения больших изменений в модуле, второй при небольших корректировках. Заметим, что если нужно полностью добавить новый модуль, то удобнее пользоваться методом AddFile или AddFromTemplate коллекции VBComponents. Методы InsertLines, DeleteLines и ReplaceLines позволяют вставить, удалить или заменить строки программного текста в указанной точке. Функция CreateEventProc позволяет создавать процедуры указанных событий. Функция Find позволяет осуществлять полномасштабный поиск в модуле.
    В каких ситуациях возникает необходимость в программном создании или корректировке кода проекта? На первый взгляд тексты программ всегда можно создавать "вручную". Однако так кажется только "на первый взгляд". На практике такая потребность возникает достаточно часто. Сейчас я рассмотрю ситуацию, в которой имеет смысл программное создание кода проекта. В этом примере я использую большинство из вышеупомянутых свойств и методов объекта CodeModule. Содержательно, задача, решаемая в примере, состоит в следующем:
    Необходимо создать шаблон документа, такой, чтобы все документы, открывающиеся на основе шаблона, содержали обработчик события Open, одинаково реагируя на каждое открытие документа. Замечу, что хотя сам шаблон может содержать обработчик этого события, но документы, открываемые на его основе, обладать этим обработчиком не будут. Однако добавить этот обработчик в документ можно программным путем, в тот момент, когда создается документ на основе шаблона. Понятно, что для этого необходимо задать соответствующий код в обработчике события New нашего шаблона. Чтобы чуть усложнить задачу и сделать ее более конкретной, будем также полагать, что документы, создаваемые на основе шаблона должны иметь переменную - счетчик, следящую за числом открытия документа. Такие счетчики полезны, когда пользователю предоставляется демо-версия, рассчитанная на фиксированное число открытий документа. Таким образом, шаблон должен гарантировать также появление переменной, назовем ее Counter, в коллекции Variables каждого нового документа, создаваемого на основе шаблона.
    Итак, наша цель состоит в том, чтобы создать шаблон документа. В обработчике события New для этого шаблона, вызываемого в тот момент, когда на основе шаблона создается новый документ, предусмотреть решение следующих задач:
  • Добавить переменную - счетчик в коллекцию Variables нового документа.
  • Добавить процедуру - обработчик события Open для нового документа.
  • Добавить стандартный модуль, содержащий процедуры, вызываемые из обработчика события Open.


  • Понятно, что, помимо всего прочего, этот пример демонстрирует программное создание кода проекта документа. Для решения этой задачи нам придется широко использовать свойства и методы объекта CodeModule, так что настал его черед. Вот код обработчика события New, встроенный в шаблон документа с именем DocWithCounter:

    Листинг 4.5.

    (html, txt)

    Процедура довольно хорошо прокомментирована, тем не менее, я позволю обратить Ваше внимание на следующие моменты

  • Вначале в коллекцию Variables нового документа вставляется переменная Counter. Напомню, что переменные этой коллекции являются частью документа, хранятся вместе с ним и потому время их жизни совпадает с временем жизни документа. Они могут выступать в роли хранителей информации между сеансами работы.
  • Интерес представляет строка:With ActiveDocument.VBProject.VBComponents("ThisDocument").CodeModule
  • Рассмотрим подробнее цепочку вызовов, порождаемую этой строкой. Заметьте, вызов ActiveDocument в обработчике события New некоторого шаблона возвращает новый документ, только что созданный на основе этого шаблона. Вызов VBProject возвращает проект этого документа. Очевидно, что содержательного кода в этом проекте пока нет. Тем не менее, в этом проекте есть модуль со стандартным именем ThisDocument, так что вызов VBComponents("ThisDocument") вернет этот модуль. Вызов CodeModule вернет объект CodeModule, содержащий пока что пустой код модуля, с которым я и начинаю работать.
  • Вызов CreateEventProc("Open", "Document") программно создаст в этом модуле обработчик события Open для объекта Document. Но пока это будет только заготовка обработчика с пустым кодом.
  • В эту заготовку я добавляю свой код. И как всегда, я строю очень простой обработчик события, состоящий из одной строчки ("OpenDoc") - вызова соответствующей процедуры стандартного модуля. Заметьте, я использую вызов метода InsertLines, чтобы вставить заготовленную в виде константы эту строку в тело обработчика события.
  • На следующем шаге я добавляю в проект документа новый модуль, даю ему имя "AddedModule" и из ранее заготовленного файла заполняю текст этого модуля. Но хочу обратить Ваше внимание, в этот момент не только добавятся процедуры, хранимые в этом файле, но и сам модуль получит имя "AddingModule" по имени модуля, экспортированного ранее в этот файл, так что моя работа по созданию имени "AddedModule" оказалась напрасной. Тем не менее, добавление текста модуля из файла проходит успешно. Приведу текст процедур, хранимых в добавляемом модуле:



  • Листинг 4.6.

    (html, txt)

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

    Программное добавление и удаление ссылок

    Теперь я хочу рассмотреть еще один пример, являющийся ответом на следующий вопрос одного из моих читателей:
    Как программно добавляются и удаляются ссылки коллекции References?
    Задача Сергея Шершнева (так зовут моего читателя) состояла в том, что, работая с системой документов, ему в зависимости от выбора пользователя необходимо было подключать программные проекты тех или иных документов. Чтобы получить доступ в главном документе к процедурам подключенных проектов, ему необходимо было предварительно программно установить ссылки на подключаемые проекты. Не буду вдаваться во все тонкости его проблем и рассмотрю лишь две конкретные задачи, как программно включить (выключить) ссылку на программный проект и как вызывать процедуры подключенных проектов.
    Для ответа на эти вопросы рассмотрим следующую ситуацию. Пусть у нас есть главный документ DocOne и два других документа DocTwo и DocThree. Все три документа обладают программными проектами. Открыв главный документ, мы интересуемся предпочтениями пользователя и, в зависимости от его выбора подключаем документ DocTwo или DocThree, вызывая затем соответствующие процедуры подключенного документа. По ходу дела необходимо также уметь отключить ранее подключенный документ. Рассмотрим одно из возможных решений этой задачи.
    Прежде, чем перейти к непосредственному решению задачи, сделаю одно замечание. Решение задачи начинается с выяснения предпочтений пользователя. Типичное решение задачи о выборе предпочтений пользователя состоит в том, что ему предъявляется форма, содержащая список всех допустимых значений, задающих предпочтения. Работая с этим списком пользователь формирует множество своих предпочтений. Сейчас я ограничусь более простой процедурой, учитывающей мой частный случай работы с документами:
    Public Sub ChooseProject(NoF As String, NoP As String) Const Msg = "Введите имя файла, хранящего документ и его программный проект!" NoF = InputBox(Msg, "Projects", "DocTwo.doc") Const Msg1 = "Введите имя проекта, хранящегося в документе!" NoP = InputBox(Msg1, "Projects", "DocTwo") End Sub

    Листинг 4.7.

    (html, txt)

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

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

    Public Sub CreateRef() 'Создание ссылки на проект с именем NoP, хранящийся в файле с именем NoF Dim MyPath As String Dim MyRef As Object Dim NameOfProject As String Dim NameOfFile As String

    'Выбор добавляемого проекта Call ChooseProject(NameOfFile, NameOfProject)

    'Запоминание глобальных переменных программы NoF = NameOfFile NoP = NameOfProject With ActiveDocument MyPath = .Path 'Вставка ссылки If Not ExistRef(NameOfProject, MyRef) Then .VBProject.References.AddFromFile MyPath & "\" & NameOfFile End If CallProcFromProject Debug.Print "Имя файла -", NoF, "Имя проекта -", NoP End With End Sub

    Листинг 4.8.

    (html, txt)

    Как видите, для подключения ссылки я использую метод AddFromFile коллекции References. В качестве параметра необходимо задать полный путь к файлу, содержащему документ с подключаемым проектом. Имя проекта при этом указывать не нужно, - оно автоматически будет найдено в документе. Обратите внимание на использование переменной MyPath, задающей путь к активному документу. Я предполагаю, что активным является мой главный документ. Второе, более сильное предположение состоит в том, что все подключаемые документы находятся в одном каталоге с главным. Это позволяет безболезненно переносить все документы на другой компьютер и размещать их в любом каталоге.

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


    Листинг 4.9.

    (html, txt)

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

    Листинг 4.10.

    (html, txt)

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

    Программное создание компонент проекта

    В следующем примере займемся анализом компонент и ссылок проекта. При этом я не ограничусь печатью свойств компонент и ссылок, но добавлю в проект новую компоненту, создав программно новую форму:
    Листинг 4.3.
    (html, txt)
    Приведу результаты отладочной печати по завершении работы этой процедуры:
    Листинг 4.4.
    (html, txt)
    Вначале до программного добавления формы печатаются компоненты проекта. Их в проекте пять - сам документ, три стандартных модуля с именами: DocOneMacros, Examples и Examples1, модуль класса с именем EventsOfApp. После программного добавления формы повторяется печать компонент, число которых, естественно, выросло на единицу за счет добавления формы. Конечно, главным итогом работы процедуры является не столько отладочная печать, сколько появление новой формы, которую можно заполнять программно или вручную.
    Приведенные примеры программной работы с проектом достаточно просты. Два более сложных примера на эту тему связаны с вопросами одного из читателей:
  • Как сохранить в форме программно добавленные элементы управления так, чтобы они появлялись при повторном ее открытии?
  • Как импортировать VBComponent, если компонент с таким именем уже присутствует в программе?

  • При ответе на первый вопрос я, во-первых, показал, как программно добавляются в созданную форму элементы управления, во-вторых, как использовать свойство Designer для придания форме, с которой работает программист, статуса режима проектирования.

    Листинг 4.4.

    Вначале до программного добавления формы печатаются компоненты проекта. Их в проекте пять - сам документ, три стандартных модуля с именами: DocOneMacros, Examples и Examples1, модуль класса с именем EventsOfApp. После программного добавления формы повторяется печать компонент, число которых, естественно, выросло на единицу за счет добавления формы. Конечно, главным итогом работы процедуры является не столько отладочная печать, сколько появление новой формы, которую можно заполнять программно или вручную.

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

  • Как сохранить в форме программно добавленные элементы управления так, чтобы они появлялись при повторном ее открытии?
  • Как импортировать VBComponent, если компонент с таким именем уже присутствует в программе?


  • При ответе на первый вопрос я, во-первых, показал, как программно добавляются в созданную форму элементы управления, во-вторых, как использовать свойство Designer для придания форме, с которой работает программист, статуса режима проектирования.

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

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

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

    Форматирование листа бланка.

    Обычный лист 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
    Листинг 6.18.
    (html, txt)
    MacroRecorder помещает все создаваемые макросы в отдельный модуль, который я переименовал, назвав его "БланкСчетФактура".

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

    Заключительный шаг в формировании шапки - заполнение полей с реквизитами офиса: адресом, телефоном и др. Визуально эту операцию выполнить просто. В одной ячейке записывается название реквизита, а в ячейке справа его значение. Обычно одной ячейки для значения не хватает, и требуется расширить это поле до нужных размеров. Чтобы не менять размеры столбцов, проще всего слить идущие подряд ячейки. Вручную для этого нужно выделить ячейки и включить переключатель MergeCells (Объединение ячеек) на вкладке Alignment (Выравнивание) из пункта Cells (Ячейки) меню Format (Формат).
    Чтобы объединить поля с реквизитами и придать им некоторую структуру, их, как правило, заключают в рамку. Подходящую рамку можно выбрать из набора готовых Shape объектов, доступ к которым можно получить, включив инструментальную панель Рисование (Drawing) с элементами рисования; одна из ее кнопок - Автофигуры (AutoShapes) - позволяет добраться до базисных Shape-фигур. Когда рамка накладывается на поля с реквизитами, поля перестают быть видимыми. Рамку следует сделать прозрачной. Для этого нужно, выделив ее, щелкнуть правой кнопкой мыши, из контекстного меню выбрать пункт FormatAuto Shape и в появившемся окне в разделе заполнения (Fill) выбрать из списка значение для цвета NoFill (без заполнения). В этот момент при желании можно изменить стиль начертания границ рамки.
    Я проделал все описанные выше операции и получил законченную шапку, отвечающую нашему эскизу. Макрос "РеквизитыИРамка" позволит проследить за моими действиями последнего этапа - формирования реквизитов офиса. Заметьте, в завершение шапки я подвел черту, подчеркнув сформированную часть яркой линией.
    Листинг 6.21.
    (html, txt)
    Макрос получился довольно большой, поскольку он оперирует с большим числом объектов, и для каждого из них перечисляются все свойства, в том числе и устанавливаемые по умолчанию. Я сократил его текст и добавил комментарии. Следует заметить, что это типичная ситуация, когда полученный макрос является основой для программиста, который оптимизирует его текст, редактирует его, вносит добавления и изменения, добиваясь нужного визуального эффекта.

    'Запись реквизитов 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

    Листинг 6.21.

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

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

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


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

    Рассмотрим вначале самый простой способ посадки пассажира в лодку. Щелчок левой кнопки мыши на видимом объекте образе одного из наших героев будет перемещать его (образ) в лодку. При этом, естественно, для того, чтобы перемещение было допустимым, нужно чтобы герой и лодка находились на одном берегу. Программно перемещение объекта в лодку делается чрезвычайно просто, для этого достаточно изменить свойства 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
    Листинг 6.4.
    (html, txt)
    Обработчики событий, как я и говорил ранее, очень простые, они вызывают соответствующую процедуру обработки события из стандартного модуля. Я реализовал две стратегии обработки, поэтому в теле обработчика предусмотрен вызов двух различных процедур. Один из этих вызовов закомментирован. Вот тексты процедур, соответствующие действующим (не закомментированным) вызовам:
    Листинг 6.5.
    (html, txt)
    Я написал четыре почти одинаковые процедуры для размещения каждого из героев в лодке. Каждому из них отведено постоянное место в лодке и координаты подобраны так, чтобы изображение казалось бы более правдоподобным. Однако с программистской точки зрения это решение может показаться не самым удачным, в особенности при большом числе объектов. Поэтому я приведу и второй вариант, когда размещение любого из объектов выполняется одной и той же процедурой, имеющей в этом случае два параметра - видимый образ объекта и его состояние:
    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
    Листинг 6.6.
    (html, txt)
    Заметьте, теперь координаты, которые получает размещаемый объект, зависят не только от расположения лодки, но и от числа пассажиров в ней. Обращение к этой процедуре в обработчике событий закомментировано. При желании можно пользоваться любым из двух приведенных вариантов.

    Рассмотрим вначале самый простой способ посадки пассажира в лодку. Щелчок левой кнопки мыши на видимом объекте образе одного из наших героев будет перемещать его (образ) в лодку. При этом, естественно, для того, чтобы перемещение было допустимым, нужно чтобы герой и лодка находились на одном берегу. Программно перемещение объекта в лодку делается чрезвычайно просто, для этого достаточно изменить свойства 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
    Листинг 6.4.
    Обработчики событий, как я и говорил ранее, очень простые, они вызывают соответствующую процедуру обработки события из стандартного модуля. Я реализовал две стратегии обработки, поэтому в теле обработчика предусмотрен вызов двух различных процедур. Один из этих вызовов закомментирован. Вот тексты процедур, соответствующие действующим (не закомментированным) вызовам:
    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

    Листинг 6.5.

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

    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

    Листинг 6.6.

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

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

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

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


    Листинг 6.7.

    (html, txt)

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

    Листинг 6.8.

    (html, txt)

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


    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


    Листинг 6.7.

    Заметьте, для четырех перемещаемых объектов: человека, волка, козы и капусты написаны четыре обработчика события 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

    Листинг 6.8.

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

    "Красивое имя"

    Рядом с логотипом поместим название офиса: "Издательство Родная Речь". Чтобы эта надпись выглядела незаурядно, воспользуемся возможностями, которые предоставляет инструментальная панель 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
    Листинг 6.20.
    (html, txt)

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

    Мы выполнили всю работу по созданию шапки, разделив ее на четыре этапа. На каждом из этих этапов Macrorecorder создал соответствующий макрос. Осталось объединить эти макросы в один, - мы назвали его "Шапка". Он очень прост: последовательно вызывает уже созданные макросы каждого шага:
    Sub Шапка() 'Объединение четырех макросов для построения шапки бланка ФорматированиеБланка Логотип КрасивоеИмя РеквизитыИРамка End Sub
    Листинг 6.22.
    (html, txt)
    Запустив этот макрос на чистом листе, мы получим заполненную шапку. Точнее, почти заполненную, поскольку макрос "Логотип" не записал действий с объектом класса Image по изменению его свойств. Так что нужно приложить некоторые усилия и повторить действия по установлению свойств объекта, чтобы увидеть картинку, связанную с объектом Image. Взгляните на результат работы макроса "Шапка":
    Макрос

    увеличить изображение
    Рис. 6.6.  Построение шапки
    on_load_lecture()
    Макрос
    Макрос
    Дальше "
    Макрос
    Если Вы заметили ошибку - сообщите нам.
    Макрос
    Страницы:
    " |
    1
    |
    2
    |
    3
    |
    4
    |
    5
    |
    6
    |
    7
    |
    8
    |
    9
    |
    вопросы | "
    |
    для печати и PDA
    Макрос
    Макрос
    Макрос

    Курсы | Учебные программы | Учебники | Новости | Форум | Помощь

    Телефон: +7 (495) 253-9312, 253-9313, факс: +7 (495) 253-9310, email: info@intuit.ru

    © 2003-2007, INTUIT.ru::Интернет-Университет Информационных Технологий - дистанционное образование


    Несколько слов о MacroRecorder

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

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

    О реализации игры

    В этой игре нет ничего, что потребовало бы привлечения специальных возможностей 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.  Форма в начальном состоянии в игре "Волк, Коза и Капуста"

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

    Итак, давайте рассмотрим, что может дать офисное программирование пользователям 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.


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

    В чем же специфика офисного программирования, чем оно отличается от программирования в программной среде таких языков как 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.



  • Подведем теперь некоторые итоги и постараемся ответить на два главных вопроса:

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


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

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

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

    По ходу игры лодка с пассажирами должна переезжать с одного берега на другой. И опять-таки, я реализовал два варианта интерфейса. В первом, более простом варианте щелчок по лодке, заставляет ее переправиться на другой берег, если соблюдены все условия игры, в лодке есть человек и никто никого не кушает. В обработчике события Click объекта Boat вызывается соответствующая процедура, которая требуемым образом изменяет координаты, как самой лодки, так и находящихся в ней пассажиров. Вот текст обработчика и вызываемой в нем процедуры Crossing:
    Листинг 6.9.
    (html, txt)
    Более интересна техника перетаскивания лодки с берега на берега. Ранее лодка служила целью назначения перетаскиваемых в нее объектов. Теперь она сама станет перемещаемым объектом и, следовательно, для нее мы создадим обработчик события 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
    Листинг 6.10.
    (html, txt)
    Как видите, ничего нового в этом обработчике нет в сравнении с уже рассмотренными подобными обработчиками, все они построены по одной схеме. Больший интерес представляет обработка событий для целевых объектов, представляющих левый или правый берег, куда причаливает лодка. Но эти объекты являются целевыми и при высадке пассажиров на берег, когда они высаживаются (перетаскиванием) на берег. Поэтому отложим их описание до следующего параграфа, где будет рассмотрена высадка пассажиров из лодки.

    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

    Листинг 6.10.

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

    и лодки Public StateOfMan As

    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 'Ширина реки, используемая при переправе
    Листинг 6.1.
    Закрыть окно

    ByVal Button As Integer, ByVal

    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
    Листинг 6.10.
    Закрыть окно

    Все пассажиры из лодки высаживаются

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

    Y As Single, ByVal DragState

    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
    Листинг 6.12.
    Закрыть окно

    Who As String) If StateOfBoat

    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
    Листинг 6.13.
    Закрыть окно

    Все пассажиры собрались на правом

    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.14.
    Закрыть окно

    Константы для организации диалогов Public

    ' Константы для организации диалогов 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 = " Лодку! "
    Листинг 6.15.
    Закрыть окно

    Private Sub

    Private Sub Shark_Click() InitialStates End Sub
    Листинг 6.16.
    Закрыть окно

    Private Sub

    Private Sub Document_Open() WGCForm.Show End Sub
    Листинг 6.17.
    Закрыть окно

    Этот макрос удаляет сетку, заголовки

    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
    Листинг 6.18.
    Закрыть окно

    Этот макрос создает элемент управления

    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
    Листинг 6.19.
    Закрыть окно

    Задаем начальные состояния объектов

    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
    Листинг 6.2.
    Закрыть окно

    Этот макрос включает инструментальную панель

    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
    Листинг 6.20.
    Закрыть окно

    Sub РеквизитыИРамка()

    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
    Листинг 6.21.
    Закрыть окно

    Объединение четырех макросов для построения

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

    Sub РеквизитыЗаказчика()

    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.23.
    Закрыть окно

    Изменение ширины полей, меняя размеры

    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
    Листинг 6.24.
    Закрыть окно

    Sub ШапкаТаблицы()

    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
    Листинг 6.25.
    Закрыть окно

    Выделение границ итоговой строки With

    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
    Листинг 6.26.
    Закрыть окно

    Sub Расчеты()

    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
    Листинг 6.27.
    Закрыть окно

    Построение таблицы СтолбцыТаблицы ШапкаТаблицы ПоследняяСтрока

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

    End Sub Сборка макросов. Макрос

    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
    Листинг 6.29.
    Закрыть окно

    InitialStates End Sub

    Private Sub UserForm_Activate() InitialStates End Sub
    Private Sub UserForm_Initialize() InitialStates End Sub
    Листинг 6.3.
    Закрыть окно

    Private Sub

    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
    Листинг 6.4.
    Закрыть окно

    и пассажир на одном берегу

    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
    Листинг 6.5.
    Закрыть окно

    Im As Image, ByRef St

    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
    Листинг 6.6.
    Закрыть окно

    ByVal Button As Integer, ByVal

    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
    Листинг 6.7.
    Закрыть окно

    Y As Single, ByVal DragState

    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
    Листинг 6.8.
    Закрыть окно

    Переправа на другой берег 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
    Листинг 6.9.
    Закрыть окно

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

    Моя цель состоит в проектировании интерфейса нашей игры. Поскольку стратегическое решение уже принято, действие игры будет разворачиваться в диалоговом окне - форме 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, который я создал первым делом:
    Листинг 6.1.
    (html, txt)
    Приведем теперь процедуру InitialStates, которая задает начальные установки для наших переменных и визуальных объектов:
    Листинг 6.2.
    (html, txt)
    Процедура InitialStates будет вызываться в ответ на такие события, как инициализация или активация нашей формы:
    Private Sub UserForm_Activate() InitialStates End Sub
    Private Sub UserForm_Initialize() InitialStates End Sub
    Листинг 6.3.
    (html, txt)
    on_load_lecture()

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

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

  • Такое разделение сделает обозримыми макросы, транслирующие мои действия в тексты на VBA.
    Построение столбцов
    Столбцы таблицы это ее поля. Размер поля, его ширина зависит от содержания поля. В Excel требуемого размера можно достичь двумя путями объединением (слиянием) ячеек, составляющих одно поле, или изменением размера соответствующего столбца Excel, отведенного для поля. Поскольку второй способ действует на всю таблицу и может привести к изменению внешнего вида уже сформатированного листа, то применять его следует с определенной осторожностью. При применении такого способа рекомендуется начинать форматирование документа с создания таблицы и соответствующего изменения размеров ее столбцов. В данном случае применяются оба способа, изменяются размеры нескольких столбцов и сливаются ячейки для поля, задающего название товара.
    Для решения задачи я выделил первую строку таблицы, слиянием ячеек и передвижкой границы между столбцами добился нужных размеров полей таблицы, а затем, используя вкладку "Границы", выделил внешние и внутренние вертикальные границы. После чего осталось скопировать формат этой строки на нужное количество строк таблицы. Вот макрос, выполняющий эти действия:
    Листинг 6.24.
    (html, txt)
    Шапка таблицы
    Шапка таблицы будет состоять из двух строк, в первой содержатся названия полей, во второй их индексы. Используя соответствующие атрибуты на вкладке Alignment (Выравнивание), я задал центрирование текста по вертикали и горизонтали, а также автоматическое изменение высоты строки, чтобы текст названия полей был полностью видимым. Кроме того, я выделил графически границы шапки, задав их двойными линиями. Макрос получается, конечно, большим, поскольку оперирует с большим числом объектов. Вот его текст:

    Листинг 6.25.

    (html, txt)

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

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

    Листинг 6.26.

    (html, txt)

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

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

    Листинг 6.27.

    (html, txt)

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

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

    Листинг 6.28.

    (html, txt)

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

    Раздел

    увеличить изображение
    Рис. 6.8.  Таблица продажи товаров бланка Счет-Фактура

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

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

    Листинг 6.29.

    (html, txt)

    Запустив этот макрос на чистом листе 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

    Листинг 6.27.

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

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

    Листинг 6.28.

    Запустив этот макрос на листе 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

    Листинг 6.29.

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

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

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

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

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

  • Вот текст соответствующего макроса, записавшего мои действия:
    Листинг 6.23.
    (html, txt)
    Запустив макрос "РеквизитыЗаказчика", на рабочем листе с уже созданной шапкой я получил следующую часть бланка:
    Реквизиты покупателя

    увеличить изображение
    Рис. 6.7.  Шапка и раздел с реквизитами заказчика

    'Реквизиты грузоотправителя и грузополучателя 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.23.

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

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

    увеличить изображение
    Рис. 6.7.  Шапка и раздел с реквизитами заказчика

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

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


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

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

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


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

    Каждый приличный офис имеет логотип. Наш офис, который мы будем называть "Родная Речь" такой логотип имеет. Под логотипом мы понимаем графический образ, являющийся символом организации и размещаемый на ее бланках. Допустим, рисунок логотипа уже создан, и нам остается только поместить его в нужном месте бланка, возможно, согласовав размеры. Есть две возможности размещения его на бланке. При создании логотипа офиса "РР" я нашел рисунок бабочки и пририсовал в графическом редакторе на каждом из ее крылышек букву "Р", затем привел рисунок к нужным размерам. Чтобы поместить его на бланк, можно вставить рисунок в ячейку рабочего листа. Для этого нужно выделить подходящую ячейку и указать имя файла в окне, раскрывающемся при выборе команды 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
    Листинг 6.19.
    (html, txt)
    Заметьте, MacroRecorder сегодня не умеет следить за действиями, выполняемыми над самим элементом управления, поэтому мои действия по работе с элементом Image не нашли отражения в макросе. Так что каждый раз после запуска макроса работу по заданию свойств элемента управления необходимо повторять заново. Перечислю те изменения, которые я задавал в окне свойств элемента управления
  • Image получил имя Логотип;
  • включив свойство Picture, в появившемся окне я указал файл, содержащий образ с логотипом (бабочку РР);
  • свойству PictureSizeMode приписал значение 3 (PictureSizeModeZoom), при котором картинка распахивается на все окно, отведенное образу;


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

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

    Рис. 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 = " Лодку! "
    Листинг 6.15.
    (html, txt)
    Такой подход позволяет при необходимости легко перейти на другой язык диалога, или сделать выбор языка параметром игры. Разумно также, но я не стал этого делать, собрать все диалоги в специальной процедуре Dialogs. Я отказался также от использования объекта Assistant при ведении диалога, хотя он очень подходит для подобных игр. Последнее решение обусловлено тем, что мне хотелось в чистом виде сохранить концепцию документа - обложки, а объект Assistant является специфическим объектом Office 2000. Его использование не позволило бы перенести игру без всяких хлопот в чистый VB.

    Нам осталось рассмотреть еще пару важных моментов, завершающих реализацию игры. Всякий раз, когда объекты игры изменяют свое состояние, необходимо проверять, является ли оно допустимым, не может ли съесть волк козу, или коза капусту. Многократно вызываемая по ходу дела процедура 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.14.
    При обнаружении критической ситуации, например, когда волк, оставленный без присмотра, съедает козу, образ козы исчезает с игрового поля и выдается соответствующее сообщение о том, что произошло ужасное событие и игра не может быть продолжена. Взгляните, как выглядит эта ситуация:
    Тестирование состояний и организация диалога

    Рис. 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 = " Лодку! "

    Листинг 6.15.

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

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

    Эта операция представляет процесс, обратный процессу посадки в лодку. Опять-таки, я реализовал две возможных стратегии. После того, как лодка причалила к берегу, одним щелчком по берегу можно высадить всех пассажиров из лодки. Вторая стратегия соответствует интуитивному поведению, когда пассажиров, не обязательно всех, можно высаживать по одному путем простого их перетаскивания на берег. Рассмотрим вначале более простую стратегию, реализованную в обработчиках событий Click для двух объектов LeftBank и RightBank:
    Private Sub LeftBank_Click() 'Все пассажиры из лодки высаживаются на левый берег ONLeftBank ("All") End Sub Private Sub RightBank_Click() 'Все пассажиры из лодки высаживаются на правый берег OnRightBank ("All") End Sub
    Листинг 6.11.
    (html, txt)
    Сами обработчики являются простыми, поскольку вся обработка спрятана в вызываемых процедурах стандартного модуля, которым в качестве параметра передается слово "All", указывающее на необходимость высадки на берег всех пассажиров. Эта же процедура будет использоваться и во второй стратегии, когда пассажиры высаживаются по одному. Давайте перейдем к ее рассмотрению. Для самих перетаскиваемых объектов обработчики события MouseMove, запускающие перетаскивание объекта, уже написаны. Мы рассматривали их, когда речь шла о перемещении объектов с берега в лодку. Теперь изменилась цель перетаскивания из лодки на берег, левый или правый в зависимости от того, куда причалила лодка. Поэтому нам надо лишь рассмотреть обработчики событий для целевых объектов LeftBank и RightBank. Напомним, эти объекты являются целевыми и при причаливании лодки к берегу. Вот тексты этих обработчиков:
    Листинг 6.12.
    (html, txt)
    Все программистские сложности спрятаны в процедурах стандартного модуля OnLeftBank и OnRightBank. Именно в них предусмотрен разбор случаев, то ли лодка причалила к берегу, то ли человек, то ли какой-либо из его спутников высаживается на берег. Здесь же предусмотрена проверка того, на какой берег в данный момент возможна высадка или причаливание.
    Листинг 6.13.
    (html, txt)

    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

    Листинг 6.12.

    Все программистские сложности спрятаны в процедурах стандартного модуля 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

    Листинг 6.13.

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

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

    Рис. 6.3.  Окончание игры "Волк, Коза и Капуста"
    Чтобы начать игру с начала, достаточно щелкнуть по объекту Shark, который до сих пор никак еще не использовался. В ответ на событие Click вызывается процедура InitialStates:
    Private Sub Shark_Click() InitialStates End Sub
    Листинг 6.16.
    (html, txt)
    Для того чтобы игра начиналась с открытием документа - обложки, зададим обработку события Open для этого документа:
    Листинг 6.17.
    (html, txt)
    На этом я завершаю описание реализации этой игры. Конечно, моя цель состояла не в том, чтобы создать совершенную реализацию данной игры. Реализация игры, скорее побочный продукт моей работы. Я выбрал эту простую игру в качестве хорошего примера, на котором я постарался показать, как создаются документы - обложки, как можно создать и использовать визуальные объекты. Что же касается самой игры, то ее несомненно можно усовершенствовать. Вот несколько возможных направлений работы для тех, кто хотел бы довести эту игру до "товарного" вида:
  • Улучшить интерфейс игры. В частности, для организации диалогов использовать объект Assistant с подходящей анимацией.
  • Ввести учет времени на перевоз всех спутников человека.
  • Усложнить игру за счет введения новых героев. В частности, акула могла бы мешать переправе участников, и нужно было бы, например, брать ружье, чтобы отпугнуть ее.

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

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

    CallBack процедура в немодальном диалоге

    Процедура обратного вызова (CallBack)- это такая процедура, которая вызывается системными, а не написанными самим программистом процедурами. Типичная ситуация такова: Программист явно или неявно вызывает системную процедуру, а та, в свою очередь, вызывает CallBack процедуру программиста. Естественно, что для CallBack процедур система определяет форму их вызова - имя процедуры, число и типы формальных параметров (но не их имена, которые могут быть произвольными).
    CallBack процедуры необходимы при немодальном диалоге, организованном объектами Balloon. Система вызывает CallBack процедуру, передавая ей в качестве одного из параметров номер кнопки, которую выбрал пользователь в окне немодального диалога. Напомним, что при модальном диалоге этот номер возвращает метод Show. Организовать модальный диалог достаточно просто, а с не модальным всегда возникают некоторые сложности. В данном случае они преодолеваются за счет использования процедуры обратного вызова. Эту процедуру пишет сам программист, но вызывает ее система, передавая в процедуру номер кнопки, известный ей в момент вызова. Используя полученный параметр, конечно, нетрудно написать тело самой процедуры так, чтобы она правильно реагировала на выбор, сделанный пользователем.
    Теперь, когда общая идея понятна, уточним некоторые детали. Прежде всего заметим, что объект Balloon имеет еще одно ранее не описанное важное свойство CallBack. Значением этого свойства является строка, задающая имя CallBack процедуры. Заметьте, что в данном случае Вы сами определяете имя процедуры и сообщаете его системе. Но число параметров и их типы все-таки фиксированы. Процедура, имя которой Вы указали в свойстве CallBack, должна иметь следующий синтаксис:
    Листинг 7.6.
    (html, txt)
    Повторяем, имена формальных параметров могут быть произвольными. Важно только понимать, что первый параметр задает объект Balloon, вызвавший немодальный диалог, второй - номер кнопки, выбранной пользователем (это может быть системная кнопка или кнопка, определенная меткой). Третий параметр, как правило, не используется в теле процедуры. Он необходим системному Wizard (Мастеру), работающему с объектом Assistant. Конечно, если Вы пишите свой собственный пользовательский Wizard, то этот параметр понадобится. В соответствии с этим синтаксисом необходимо написать тело процедуры, определяющее реакцию на выбор пользователя. Процедуру естественно следует поместить в тот же модуль, в котором задается немодальный диалог. Процедура будет вызываться в тот момент, когда пользователь щелкает выбранную им кнопку в окне немодального диалога. Напомним, что до закрытия пользователь может многократно входить и выходить из этого окна, выполняя другие работы. В заключение приведем пример немодального диалогового окна:
    Листинг 7.7.
    (html, txt)
    Здесь, как Вы видите, вместо переменной Answer появилась CallBack процедура Answer, которая и определяет реакцию на выбор пользователя. Заметьте, что в конце этой процедуры метод Close закрывает диалог. Если этого не делать, то после выдачи сообщения о неудачном решении, можно вернуться в окно немодального диалога и изменить свой выбор! В этом сила немодального диалога. Тем не менее, следует быть весьма аккуратным и во время закрыть окно немодального диалога. В противном случае это окно будет постоянно (до перезагрузки компьютера) сопровождать Помощника на экране. Сложность состоит в том, что локальная переменная, определяющая объект Balloon, может прекратить свое существование, а немодальное окно будет продолжать существовать. Поэтому лучше такие переменные определять как глобальные переменные модуля, тогда метод Close может быть вызван в любой из процедур, завершающих работу приложения.

    Public Sub UnModal() Dim FirstBall As Balloon Dim Text1 As String, Text2 As String Dim Text3 As String, Text4 As String Dim myPath As String myPath = ActiveDocument.Path 'Формирование свойства Text 'Вставка графики в начало текста Text1 = "{wmf " & myPath & " /Cabbage.wmf}" Text2 = "Вы уже перевезли на другой берег " Text3 = "Волка и Козу! Осталось съездить за капустой." Text4 = "Отправляясь назад, Вы возьмете с собой: " Set FirstBall = Assistant.NewBalloon With FirstBall .Mode = msoModeModeless .Callback = "Answer" .Icon = msoIconAlert .Heading = "ВОЛК, КОЗА И КАПУСТА" .Text = Text1 & Text2 & Text3 & Text4 .BalloonType = msoBalloonTypeButtons .Button = msoButtonSetNone .Labels(1).Text = "Волка" .Labels(2).Text = "Козу" .Labels(3).Text = "Капусту" .Labels(4).Text = "Никого" .Show End With End Sub

    Public Sub Answer(Ball As Balloon, Count As Long, iPriv As Long) 'Анализ принятого решения Select Case Count Case 1 Call Explain("Это возможное, но не лучшее решение!") Case 2 Call Explain("Это правильно!") Case 3 Call Explain("Это невозможно!") Case 4 Call Explain("Ужасная трагедия: Волк съест Козу!") Case Else 'Пропускаем End Select ' При желании можно закрыть окно диалога Ball.Close End Sub

    Public Sub Explain(Txt As String) Dim ExplainBall As Balloon Set ExplainBall = Assistant.NewBalloon With ExplainBall .Heading = "ВОЛК, КОЗА И КАПУСТА" .Text = Txt .Show End With End Sub

    Листинг 7.7.

    Здесь, как Вы видите, вместо переменной Answer появилась CallBack процедура Answer, которая и определяет реакцию на выбор пользователя. Заметьте, что в конце этой процедуры метод Close закрывает диалог. Если этого не делать, то после выдачи сообщения о неудачном решении, можно вернуться в окно немодального диалога и изменить свой выбор! В этом сила немодального диалога. Тем не менее, следует быть весьма аккуратным и во время закрыть окно немодального диалога. В противном случае это окно будет постоянно (до перезагрузки компьютера) сопровождать Помощника на экране. Сложность состоит в том, что локальная переменная, определяющая объект Balloon, может прекратить свое существование, а немодальное окно будет продолжать существовать. Поэтому лучше такие переменные определять как глобальные переменные модуля, тогда метод Close может быть вызван в любой из процедур, завершающих работу приложения.

    Как Рокки ведет диалог?

    Я уже сказал, что никаких серьезных трудностей не возникло при введении Помощника для организации диалога. Достаточно просто, заменить сообщение, выдаваемое функцией MsgBox на сообщение, выдаваемое Помощником, используя его баллончики. Но здесь есть одно "но"! При работе с Помощником появляется больше возможностей и, поневоле, хочется организовать более живой и интересный диалог - использовать картинки, дать пользователю возможность выбора, проявить интеллект и предлагать подсказки в возникающих ситуациях.
    О том, как в сообщения, появляющиеся в баллончиках, включать рисунки, было уже рассказано в этой лекции. Было также достаточно подробно рассказано и о том, как дать возможность пользователю выбирать и как затем анализировать его выбор. Поэтому давайте поговорим об "интеллекте" Помощника. Это трудная задача. Но в нашем простом примере она решаема. Давайте возложим на Помощника обязанность не только следить за соблюдением правил игры и выдавать сообщения при их нарушениях, но и пусть он дает советы, подсказывая, какой ход следует сделать в той или иной ситуации.
    Общий подход решения последней задачи понятен, - необходимо следить за состоянием системы, в нашем случае игры. Когда пользователь обращается к Помощнику за советом, то, зная текущее состояние S, можно попытаться найти разумное решение R. Иногда, как в нашей простой игре, существует точное решение и, зная S , можно однозначно определить R, как функцию от S. В тех случаях, когда для поиска решения необходим перебор вариантов, проведение сложных вычислений или просто квалификация пользователя невысока, советы Помощника могут быть весьма ценными. Интеллектуальные Помощники незаменимы в играх и обучающих системах. Конечно, в серьезных задачах квалифицированный пользователь может принимать лучшие решения, но и там подсказки Помощника могут быть полезными.
    В игре "Волк, Коза и Капуста" достаточно было ввести одну переменную, следящую за состоянием игры, изменять ее значение по ходу игры, и при обращении к Помощнику за советом, выдавать подсказку на основе анализа значения этой переменной.
    Вот какие изменения пришлось сделать в реализации игры. Во-первых, я добавил новый модуль Dialogs, в который поместил процедуры и константы, необходимые для ведения диалога. Некоторые изменения пришлось ввести и в текст модуля WGC. В раздел объявлений этого модуля добавлены следующие строчки:

    Листинг 7.8.

    (html, txt)

    Изменения внесены и в процедуру инициализации:

    Листинг 7.9.

    (html, txt)

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

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

    Листинг 7.10.

    (html, txt)

    Приведем теперь текст модуля Dialogs:

    Листинг 7.11.

    (html, txt)

    Я не буду комментировать процедуры этого модуля. Все что нужно было сказать, уже сказано. Отмечу только процедуру Dialog9, которая вызывается, когда Рокки должен дать совет. Заметьте, его интеллектуальность определяется двумя правилами типа "если… то". Если не выполняются условия ни одного из этих правил, то Рокки полагает, что ситуация очень простая и его советы не нужны, поскольку можно принимать любое решение, допустимое правилами игры.

    На этом мы и закончим разговор об организации диалогов с использованием возможностей объекта Assistant.

    Метки и флажки

    У объекта Balloon есть две коллекции - Labels с элементами Label и CheckBoxes с элементами CheckBox. В каждой из коллекций может содержаться не более пяти элементов. Элементами Labels являются метки, для которых, как правило, задается свойство Text, определяющее метку. Метки имеют тип, задающий их оформление. Все метки должны быть одного типа, так что настала пора рассказать еще об одном свойстве объекта Balloon. Тип меток задается свойством BalloonType объекта Balloon и имеет три возможных значения:
  • msoBalloonTypeNumbers - нумерованный список меток,
  • msoBalloonTypeBullets- список-бюллетень меток,
  • msoBalloonTypeButtons - список кнопок

  • Списки первых двух типов позволяют проинформировать пользователя о тех или иных предоставляемых возможностях. Конечно, для этой цели можно также использовать свойство Text самого объекта Balloon, но метки удобнее, поскольку информация предоставляется в структурированном виде.
    Третий тип меток используется для получения информации от пользователя, который выбирает одну из нескольких (до пяти) возможностей, щелчком соответствующей кнопки. Метод Show, возвращающий номер нажатой кнопки, позволяет узнать, какая кнопка была выбрана. Тем самым появляется возможность организовать дальнейшие действия в зависимости от сделанного выбора.
    Вместе или вместо меток третьего типа можно использовать свойство Button, благодаря которому в баллончике также появляются кнопки, а метод Show и в этом случае возвращает номер нажатой кнопки. Кнопки со стандартными именами, задаваемые Button, часто бывают полезными, но, конечно, не всегда приемлемы. Благодаря свойству Text у кнопок - меток, можно задать "свои" кнопки со своими именами.
    Совместное использование меток третьего типа и свойства Button стало теперь возможным - я приведу пример такого использования.
    Флажки (кнопки выбора), задаваемые элементами CheckBox, используются для той же цели, что и кнопки - они позволяют определить выбор пользователя. Существенное отличие состоит в том, что пользователь может выбрать не одну из предоставляемых ему возможностей, а несколько, взводя соответствующие флажки. Когда флажок взведен, то свойство Checked соответствующего элемента из коллекции CheckBoxes получает значение True. Каждое из этих свойств можно затем проверить и использовать при выборе последующих действий в программе.
    Приведем пример, в котором действуют два объекта Balloon. Первый из них выясняет увлечения, используя флажки, второй - уведомляет об этих увлечениях, используя список меток в форме бюллетеня.

    Листинг 7.4.

    (html, txt)

    А теперь взгляните на картинки, которые будут появляться в процессе выполнения этой процедуры.

    Метки и флажки

    Рис. 7.4.  Объект Balloon выясняет увлечения

    Метки и флажки

    Рис. 7.5.  Объект Balloon уведомляет об увлечениях

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

    Листинг 7.5.

    (html, txt)

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

    Метки и флажки

    увеличить изображение
    Рис. 7.6.  Организация выбора одной из многих возможностей

    Методы объекта Assistant

    В Office 2000 не произошло изменений в методах объекта Assistant. Он по-прежнему имеет следующие методы: StartWizard, ActivateWizard, EndWizard, Help, Move и ResetTips. Методы этого объекта используются реже, чем свойства и я ограничусь их кратким описанием.
    Методы StartWizard, ActivateWizard, EndWizard применяются при создании пользовательских Мастеров. Первым должен быть запущен метод StartWizard, возвращающий идентификатор (длинное целое) Помощника. Этот идентификатор затем используется в методах ActivateWizard и EndWizard, соответственно активизирующих и деактивирующих Помощника.
    Метод Help выводит стандартное сообщение Помощника справочной системы Office 2000 с заголовком "What would you like to do?"
    Метод Move перемещает Помощника в указанную параметрами left и top позицию на экране
    Метод ResetTips очищает журнал советов, в связи с чем могут вновь появляться уже данные советы.
    В заключение рассказа о свойствах и методах объекта Assistant приведем пример, в котором поменяем образ объекта Assistant и некоторые его свойства:
    Public Sub ChooseAss() With Assistant Debug.Print .FileName 'Задаем образ Помощника '.FileName = "dot.acs" .FileName = "Offcat.acs" '.FileName = "Rocky.acs"
    'Его анимационное поведение .Animation = msoAnimationGreeting .Animation = msoAnimationGetAttentionMinor 'Другие свойства .AssistWithHelp = True .FeatureTips = True .GuessHelp = True .HighPriorityTips = True .Visible = True End With End Sub
    Листинг 7.1.
    (html, txt)
    Взгляните на кошечку, которая будет сопровождать теперь выдачу советов и справок. Задавая свойство Animation, можно подобрать ее поведение, отвечающее данному моменту.
    Методы объекта Assistant

    увеличить изображение
    Рис. 7.2.  "Мурка" в роли Помощника (объекта Assistant)
    Обратите внимание, - на вопрос, заданный на естественном русском языке, выдана точная справка. Этого не было в предыдущей версии.

    Модальные и немодальные баллончики

    Объекты Balloon предназначены для ведения диалога с пользователем и фактически являются специальным видом диалоговых окон. Любое диалоговое окно, а значит и объект Balloon может быть модальным и немодальным. Из модальных окон нельзя выйти, не закончив диалог. Обычно, для этого нужно щелкнуть одну из кнопок этого окна - Ok или Cancel. В модальном объекте Balloon эти кнопки, как правило, присутствуют, но их наличие не является обязательным. Если щелкнуть любую кнопку в модальном окне, то диалог оканчивается и окно закрывается. До тех пор, пока модальное окно не будет закрыто, нельзя начать работать с другим окном. Немодальные окна можно покидать, в этот момент они перестают быть активными, но остаются на экране и не закрываются. При необходимости в них можно вернуться и продолжить работу с ними. Позже мы рассмотрим механизм закрытия немодальных окон и специальный механизм CallBack функций, позволяющий организовать реакцию на выбор, сделанный пользователем в немодальном окне.
    Объект Balloon имеет свойство Mode с тремя возможными значениями:
  • MsoModeModal - в этом случае диалог с объектом Balloon является модальным. Его обычно используют для вывода предупреждений, требующих особого внимания или получения ответа от пользователя, без которого дальнейшая работа не может быть продолжена.
  • MsoModeModeless - в этом случае окно становится немодальным и оно может оставаться открытым, в то время как продолжается работа над приложением в других его окнах. Такие объекты удобны, например, при выдаче справочной информации, которая должна быть все время под рукой.
  • MsoModeAutoDown - задает специальный вид модального окна, в котором не обязательны кнопки. Окно закрывается автоматически, как только пользователь введет символ с клавиатуры или щелкнет кнопку мыши, где - либо вне модального окна. Такие окна полезны при выводе сообщений, не носящих критический характер, например советов.


  • Несерьезный интерфейс для серьезных задач

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

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

    увеличить изображение
    Рис. 7.8.  Рокки сообщает о правилах игры
    Несерьезный интерфейс для серьезных задач

    увеличить изображение
    Рис. 7.9.  Рокки дает совет

    Объект Balloon

    Можно изменить свойства объекта Assistant, но в каждый момент существует только один такой объект. C ним можно связать произвольное число объектов Balloon, которые я называю баллончиками. Поясним происхождение термина. Реплики рисованных персонажей комиксов или карикатур художники нередко помещают в кружок - "баллончик", выдуваемый изо рта.
    У объекта Assistant нет коллекции объектов Balloon (а жаль), но можно создать необходимое число переменных этого класса или массив из таких объектов. Вот пример:
    Здесь созданы две переменные и массив из трех элементов класса Balloon. По ходу дела внешний вид объекта Assistant меняется, но все баллончики одинаковы, в момент рождения они пусты и имеют единственную кнопку "ОК". Рассмотрим, как можно наполнить содержанием эти объекты и задать их свойства.

    Помощник, ведущий диалог

    Всякий раз, когда разработчик создает очередное решение, одна из серьезных проблем, стоящих перед ним, состоит в организации интерфейса своей системы. Хороший интерфейс, - привлекательный и понятный пользователю, во многом определяет успех предлагаемого решения. Диалог с пользователем - важная часть интерфейса. Во многих случаях для организации диалога достаточно обычных диалоговых окон, открываемых в нужный момент с выдачей подходящего сообщения. Зачастую для организации диалога предпочтительнее более выразительные средства и тут незаменим Помощник (объект Assistant), хорошо знакомый пользователям Office 2000, поскольку именно он обеспечивает диалог со справочной системой, может организовать подсказки и выдачу советов. Давайте познакомимся с объектом Assistant поближе и научимся использовать его в своих решениях. Объект Assistant, задающий Помощника, появился в предыдущей версии Office 97. В новой версии он сохранил свои основные свойства и поведение.
    Вначале несколько слов о Помощнике. Идея Помощников (Мастеров, Агентов) в программировании не нова. Сегодня эта технология приобрела широкую популярность. В любом достаточно сложном приложении, в котором пользователю отводится активная роль, полезно создать некоторую совокупность Помощников пользователя. Пользователь будет выбирать, какую задачу он хочет решить в тот или иной момент, а соответствующий Помощник поможет ему на этом пути, проводя пользователя через все этапы решения задачи, снабжая необходимой информацией, информируя о возникших ошибках, позволяя сделать откат и вернуться к предыдущему этапу. В программных продуктах Microsoft, например, подобные Помощники или Мастера (Assistants, Wizards) сопровождают нас на каждом шагу, начиная с этапа инсталляции приложения. Кроме того, что Помощники предназначены для обеспечения помощи в решении некоторой конкретной задачи и могут вести диалог с пользователем, они могут обладать еще двумя важными качествами:
  • Иметь видимый образ, возможно допускающий анимацию. Помощник с анимацией, делает приложение одушевленнее. Замечу, что из всех образов, в которых может предстать Помощник, заданный объектом Assistant, я предпочитаю Rocky и позволяю себе в минуты отдыха играться с ним, заставляя его выполнять различные движения. Жаль только, что его анимация не подчиняется голосовым командам, например, "Принеси палку".
  • Обладать определенной интеллектуальностью. Интеллектуальный" Помощник должен уметь собирать факты в процессе работы пользователя, иметь базу знаний, содержащую факты и правила, и иметь встроенную машину вывода. Такой Помощник может делать собственные выводы, брать на себя часть творческой работы, например, по заданию части параметров, или приходить на помощь в нужный, по его мнению, момент.


  • В последнем предложении я не поставил запятые в обороте "по его мнению". Интеллектуальный Помощник, каковым, несомненно, является встроенная в Word система проверки правописания, тут же выдала мне соответствующую подсказку.

    Помощник, встроенный в справочную систему Office 2000, и построенный на основе объекта Assistant, обладает всеми тремя качествами, - выдает справки, имеет видимый образ, и не один, и обладает интеллектуальностью. Следует отметить, что его интеллектуальные способности оставляют желать лучшего, но, несомненно, улучшились в сравнении с предыдущей версией. Он теперь, по крайней мере, понимает запросы, сделанные не только на английском языке, но и на русском.

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

    Начнем рассмотрение объекта Assistant и связанных с ним объектов, с формального изучения его свойств. Структура объекта Assistant довольно проста:

    Помощник, ведущий диалог

    Рис. 7.1.  Структура объекта Assistant

    В объект Assistant вложен объект Balloon. В свою очередь в объект Balloon вложены две коллекции - CheckBoxes (кнопки выбора) и Labels (метки) с элементами классов CheckBox и Label соответственно. Обратите внимание, коллекции называются именно так и не имеют приставки Balloon, как показано на рисунке, взятом из документации, и не изменившимся в новой версии.

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

    True End With End

    Public Sub ChooseAss() With Assistant Debug.Print .FileName 'Задаем образ Помощника '.FileName = "dot.acs" .FileName = "Offcat.acs" '.FileName = "Rocky.acs"
    'Его анимационное поведение .Animation = msoAnimationGreeting .Animation = msoAnimationGetAttentionMinor 'Другие свойства .AssistWithHelp = True .FeatureTips = True .GuessHelp = True .HighPriorityTips = True .Visible = True End With End Sub
    Листинг 7.1.
    Закрыть окно

    Mes13 Call Dialog5 InitialStates End

    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 Call Dialog5 InitialStates End If 'Коза съела капусту If (StateOfCabbage = StateOfGoat) And (StateOfCabbage < < StateOfMan) Then .Cabbage.Visible = False 'MsgBox Prompt:=Mes1 + vbCrLf + Mes4, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 Call Dialog6 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 Call Dialog7 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 Call Dialog8 End If End With End Sub
    Листинг 7.10.
    Закрыть окно

    Константы для организации диалогов Public

    Option Explicit ' Константы для организации диалогов 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 = " Лодку! " Public Const Txt1 = "Привет! Я Рокки." & vbCrLf _ & "Щелкни по острову, если захочешь поговорить со мной." Public Const Txt2 = "Волк, Коза и Капуста!" 'Public Const Txt3 = "{wmf " & MyPath & "\goat.wmf}" 'Public Const Txt4 = "{wmf " & MyPath & "\cabbage.wmf}" Public Const Txt5 = ""
    'Массив баллончиков - диалоговых окон Рокки Public RockyBalloons(1 To 10) As Balloon Public Sub Dialog() 'Начальный диалог при открытии игры 'Анализ состояния игры Select Case StateOfPlay Case "InitState" Call Dialog1 Case "State1" Call Dialog2 Case "State2" Call Dialog9 Case "StateFinish" Call Dialog10 Case Else End Select
    End Sub Public Sub OneBall() Dim Text1 As String, Text2 As String, Text3 As String Dim Text4 As String, Text5 As String Dim MyPath As String MyPath = ActiveDocument.Path 'Формирование свойства Text Text1 = "Хочешь знать, как перевезти с берега на берег" ' Вставка графики в середину текста Text2 = "{wmf " & MyPath & "\wolf.wmf}" & " Волка," & vbCrLf Text3 = "{wmf "& MyPath & "\goat.wmf}" & " Козу" & " И " Text4 = "{wmf " & MyPath & "\cabbage.wmf}" & " Капусту?" & vbCrLf & vbCrLf 'Text5 = "Соблюдая 3 правила?" Dim FirstBall As Balloon Set FirstBall = Assistant.NewBalloon With FirstBall .Heading = "Волк, Коза и Капуста" .Icon = msoIconAlert .Text = Text1 & Text2 & Text3 & Text4 .Button = msoButtonSetYesNo .Show End With End Sub
    Public Sub FormBalloons() 'Инициализация баллончиков Dim I As Integer For I = 1 To 10 Set RockyBalloons(I) = Assistant.NewBalloon RockyBalloons(I).Heading = Txt2 RockyBalloons(I).Icon = msoIconAlertInfo Next I FormBall1 FormBall2 FormBall3 FormBall4 FormBall5 FormBall6 FormBall7 'FormBall8 End Sub Public Sub FormBall1() 'Формирование свойства Text - знакомство With RockyBalloons(1) .Text = Txt1 .Button = msoButtonSetOK End With End Sub
    Public Sub Dialog1() RockyBalloons(1).Show StateOfPlay = "State1" 'Игра началась End Sub Public Sub FormBall2() Dim Text1 As String, Text2 As String, Text3 As String Dim Text4 As String, Text5 As String 'Формирование свойства Text Text1 = "Нужно срочно перевезти с берега на берег" ' Вставка графики в середину текста Text2 = "{wmf " & MyPath & "\wolf.wmf}" & " Волка," & vbCrLf Text3 = "{wmf " & MyPath &"\goat.wmf}" & " Козу" & " И " Text4 = "{wmf " & MyPath & "\cabbage.wmf}" & " Капусту?" & vbCrLf & vbCrLf Text5 = "Хочешь знать правила игры?" With RockyBalloons(2) .Text = Text1 & Text2 & Text3 & Text4 & Text5 .Button = msoButtonSetYesNo End With End Sub
    Public Sub Dialog2() Answer = RockyBalloons(2).Show Select Case Answer Case msoBalloonButtonYes 'Нажата кнопка Yes 'Показать правила игры Call Dialog3 Case msoBalloonButtonNo 'Нажата кнопка No 'Предложить начать играть Call Dialog4 End Select End Sub
    Public Sub FormBall3() Dim Text1 As String, Text2 As String, Text3 As String Dim Text4 As String, Text5 As String 'Формирование переменных Text Text1 = "В лодке, используемой для перевоза," & _ " может находиться не более двух путников" & _ " (человек, волк, коза и капуста - это путники)." & vbCrLf _ & "При попытке посадить большее число путников лодка перевернется. " Text2 = "Если оставить в лодке или на берегу волка и козу без присмотра человека," & _ vbCrLf &"то волк немедленно съест козу." Text3 = "Если оставить в лодке или на берегу капусту и козу без присмотра человека," & _ vbCrLf & "то коза немедленно съест капусту." Text4 = "Без человека лодка не может переплыть на другой берег." Text5 = "Начинайте играть! Желаю успеха!" With RockyBalloons(3) .BalloonType = msoBalloonTypeNumbers .Labels(1).Text = Text1 .Labels(2).Text = Text2 .Labels(3).Text = Text3 .Labels(4).Text = Text4 .Labels(5).Text = Text5 End With End Sub
    Public Sub Dialog3() RockyBalloons(3).Show StateOfPlay = "State2" 'Правила заданы End Sub
    Public Sub FormBall4() RockyBalloons(4).Text = "Начинайте играть! Желаю успеха!" End Sub Public Sub Dialog4() RockyBalloons(4).Show StateOfPlay = "State2" 'Правила заданы End Sub
    Public Sub FormBall5() RockyBalloons(5).Icon = msoIconAlertCritical
    End Sub Public Sub Dialog5() RockyBalloons(5).Text = Mes1 + vbCrLf + Mes3 RockyBalloons(5).Show StateOfPlay = "State1" 'Начало игры End Sub
    Public Sub Dialog6() RockyBalloons(5).Text = Mes1 + vbCrLf + Mes4 RockyBalloons(5).Show StateOfPlay = "State1" 'Начало игры End Sub Public Sub Dialog7() RockyBalloons(5).Text = Mes1 + vbCrLf + Mes5 RockyBalloons(5).Show StateOfPlay = "State1" 'Начало игры End Sub Public Sub FormBall6() RockyBalloons(6).Icon = msoIconAlert RockyBalloons(6).Text = Mes2 + vbCrLf + Mes6 End Sub Public Sub Dialog8() RockyBalloons(6).Show StateOfPlay = "StateFinish" 'Конец игры End Sub
    Public Sub Dialog9() 'Совет!
    'Rule1: Если волк и коза на одном берегу, то увези козу. 'Rule2: Если капуста и коза на одном берегу, то увези капусту. If (StateOfWolf = StateOfGoat) Then RockyBalloons(7).Text = "Увези козу!" ElseIf (StateOfCabbage = StateOfGoat) Then RockyBalloons(7).Text = "Увези капусту!" Else RockyBalloons(7).Text = "Тут моя помощь не нужна!" End If RockyBalloons(7).Show
    End Sub
    Public Sub FormBall7() RockyBalloons(7).Icon = msoIconTip End Sub Public Sub Dialog10() RockyBalloons(6).Text = "Приходите, поиграем еще раз! Рокки будет ждать." RockyBalloons(6).Show StateOfPlay = "StateFinish" 'Конец игры End Sub
    Листинг 7.11.
    Закрыть окно

    Dim Text1 As String, Text2

    Public Sub OneBall() Dim Text1 As String, Text2 As String, Text3 As String Dim Text4 As String, Text5 As String Dim myPath As String myPath = ActiveDocument.Path 'Формирование свойства Text Text1 = "Хочешь знать, как перевезти с берега на берег" 'Вставка графики в середину текста Text2 ="{wmf " & myPath & "\wolf.wmf}"; & " Волка," & vbCrLf Text3 = "{wmf " & myPath & "\goat.wmf}" & " Козу" & " И " Text4 = "{wmf " & myPath & "\cabbage.wmf}" & " Капусту" & vbCrLf & vbCrLf Text5 = "Соблюдая 3 правила?" Dim FirstBall As Balloon Set FirstBall = Assistant.NewBalloon With FirstBall .Heading = "Волк, Коза и Капуста" .Icon = msoIconAlert .Text = Text1 & Text2 & Text3 & Text4 & Text5 .Button = msoButtonSetYesNo .Show End With End Sub
    Листинг 7.3.
    Закрыть окно

    Dim FirstBall As Balloon, LastBall

    Public Sub CheckingHobbies() Dim FirstBall As Balloon, LastBall As Balloon Dim i As Integer 'Первый баллончик выясняет увлечения Assistant.FileName = "Rocky.acs" Set FirstBall = Assistant.NewBalloon With FirstBall .Heading = "Ваши увлечения" .Checkboxes(1).Text = "Футбол" .Checkboxes(2).Text = "Теннис" .Checkboxes(3).Text = "Шахматы" .Checkboxes(4).Text = "Плавание" .Checkboxes(5).Text = "Горные лыжи" .Show End With 'Второй баллончик уведомляет об увлечениях Assistant.FileName = "OffCat.acs" Set LastBall = Assistant.NewBalloon Dim Count As Integer Count = 0 With LastBall .Heading = "Ваши увлечения" .BalloonType = msoBalloonTypeBullets For i = 1 To 5 If FirstBall.Checkboxes(i).Checked Then Count = Count + 1 .Labels(Count).Text = FirstBall.Checkboxes(i).Text End If Next i If Count > 0 Then .Text = "Вы любите:" Else .Text = "Вы ничего не любите" End If .Show End With End Sub
    Листинг 7.4.
    Закрыть окно

    В данном примере для задания

    Public Sub CheckingHobbies1() ' В данном примере для задания предпочтений используются метки 'и возможности анализа результата работы метода Show Dim FirstBall As Balloon, LastBall As Balloon Dim i As Integer Dim Answer As MsoBalloonButtonType 'баллончик выясняет увлечения Set FirstBall = Assistant.NewBalloon With FirstBall .Heading = "Ваш любимый вид спорта?" .BalloonType = msoBalloonTypeButtons .Labels(1).Text = "Футбол" .Labels(2).Text = "Теннис" .Labels(3).Text = "Шахматы" .Labels(4).Text = "Плавание" .Labels(5).Text = "Горные лыжи" .Button = msoButtonSetBackNextClose Answer = .Show End With 'Анализ пользовательского выбора Select Case Answer Case 1 MsgBox ("Футбол - любимая игра миллионов.") Case 2 MsgBox ("Теннис - красивый вид спорта.") Case 3 MsgBox ("Шахматы - игра мыслителей.") Case 4 MsgBox ("Плавание - нет ничего полезнее.") Case 5 MsgBox ("Горные лыжи - спорт избранных.") Case msoBalloonButtonBack MsgBox ("Показать предыдущий список игр?") Case msoBalloonButtonNext MsgBox ("Показать следующий список игр?") Case Else MsgBox ("Вы совсем не спортсмен!")
    End Select End Sub
    Листинг 7.5.
    Закрыть окно

    Ball As Balloon, NumberOfButton As

    < имя процедуры> ( Ball As Balloon, NumberOfButton As Long, iPriv As Long)
    Листинг 7.6.
    Закрыть окно

    Dim FirstBall As Balloon Dim

    Public Sub UnModal() Dim FirstBall As Balloon Dim Text1 As String, Text2 As String Dim Text3 As String, Text4 As String Dim myPath As String myPath = ActiveDocument.Path 'Формирование свойства Text 'Вставка графики в начало текста Text1 = "{wmf " & myPath & " /Cabbage.wmf}" Text2 = "Вы уже перевезли на другой берег " Text3 = "Волка и Козу! Осталось съездить за капустой." Text4 = "Отправляясь назад, Вы возьмете с собой: " Set FirstBall = Assistant.NewBalloon With FirstBall .Mode = msoModeModeless .Callback = "Answer" .Icon = msoIconAlert .Heading = "ВОЛК, КОЗА И КАПУСТА" .Text = Text1 & Text2 & Text3 & Text4 .BalloonType = msoBalloonTypeButtons .Button = msoButtonSetNone .Labels(1).Text = "Волка" .Labels(2).Text = "Козу" .Labels(3).Text = "Капусту" .Labels(4).Text = "Никого" .Show End With End Sub
    Public Sub Answer(Ball As Balloon, Count As Long, iPriv As Long) 'Анализ принятого решения Select Case Count Case 1 Call Explain("Это возможное, но не лучшее решение!") Case 2 Call Explain("Это правильно!") Case 3 Call Explain("Это невозможно!") Case 4 Call Explain("Ужасная трагедия: Волк съест Козу!") Case Else 'Пропускаем End Select ' При желании можно закрыть окно диалога Ball.Close End Sub
    Public Sub Explain(Txt As String) Dim ExplainBall As Balloon Set ExplainBall = Assistant.NewBalloon With ExplainBall .Heading = "ВОЛК, КОЗА И КАПУСТА" .Text = Txt .Show End With End Sub
    Листинг 7.7.
    Закрыть окно

    Состояние игры Public Answer As

    Public StateOfPlay As String ' Состояние игры Public Answer As MsoBalloonButtonType 'Выбор пользователя 'Другие общие параметры Public WidthOfRiver 'Ширина реки Public MyPath As String 'каталог, хранящий исходный документ и другие файлы игры
    Листинг 7.8.
    Закрыть окно

    Инициализация объектов формы With WGCForm

    Public Sub InitialStates() 'Задаем начальные состояния объектов и их образов StateOfMan = "LeftBank" StateOfWolf = "LeftBank" StateOfGoat = "LeftBank" StateOfCabbage = "LeftBank" StateOfBoat = "LeftBank" CountInBoat = 0 StateOfPlay = "InitState" ' Инициализация объектов формы 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 'Поместить Рокки на остров Assistant.FileName = "Rocky.acs" 'Debug.Print Assistant.Left, Assistant.Top Assistant.Move XLeft:=.Left + .Island.Left + .Island.Width / 2, _ YTop:=.Top + .Island.Top + .Island.Height - 10 Assistant.Animation = msoAnimationCharacterSuccessMajor 'Assistant.AssistWithAlerts = False 'Assistant.AssistWithHelp = False 'Debug.Print Assistant.Left, Assistant.Top
    End With 'Путь к каталогу MyPath = ActiveDocument.Path 'Сформировать баллончики - диалоговые окна Call FormBalloons 'Инициализировать диалог Call Dialog1
    End Sub
    Листинг 7.9.
    Закрыть окно

    Свойства объекта Assistant

    Как это мы уже не раз делали, зададим сводку основных свойств таблицей:

    Таблица 7.1. Основные свойства объекта Assistant СвойствоНазначение
    AnimationВозвращает или устанавливает тип анимации образа Assistant. Если свойство применяется к объекту Assistant, то он "оживает" сразу же при включении. Если же оно применяется к объекту Balloon, то анимация возникает только при появлении баллончика. Может быть значением одной из 34-х констант типа MsoAnimationType.
    AssistWithAlerts, AssistWithHelp, AssistWithWizardsБулевы свойства, имеющие значение True, если Assistant используется для выдачи предупреждений (заменяя окно MsgBox), в ответ на нажатие клавиши F1 (Help), и при выдаче справочных сообщений, когда работают различные Мастера
    BalloonErrorВозвращает значение, указывающее на тип последней ошибки объекта Balloon.
    FeatureTipsБулево свойство, которому дается значение True, когда желательно получать советы о наиболее эффективных способах использования свойств приложения.
    FileNameВозвращает или устанавливает имя файла, содержащего графический образ Assistant. Файл имеет уточнение act, например файл со скрепкой - Clippit.act. Заметим, что могут быть заданы только стандартные для Office 97 файлы. Собственные образы задавать нельзя, но число стандартных образов расширяется.
    GuessHelpЭто именно то булево свойство, установив значение которого, равным True, Вы получаете контекстную подсказку с именами разделов электронной документации при обращении за помощью. Хотите сами искать - отключите это свойство.
    HighPriorityTips, KeyboardShortcutTips, MouseTipsСоветы имеют разный приоритет. Если свойство имеет значение True, то будут выдаваться советы только наивысшего приоритета. Следующие два булевы свойства включают возможность выдачи информации о горячих клавишах и наиболее эффективном использовании мыши.
    NewBalloonЯвляется по существу центральным свойством, поскольку позволяет создать новый объект Balloon. После чего можно нужным образом определить свойства этого объекта. Напомним, что баллончик определяет основные возможности Assistant.
    SoundsБулево свойство, включающее звук в процессе анимации.
    TipOfDayБулево свойство, включающее или отключающее "Совет дня" при открытии приложения Office 97.
    VisibleДелает видимым или невидимым объект Assistant, сворачивая его в кнопку на панели инструментов.
    Новые свойства в Office 2000 Назначение
    OnНовое булево свойство позволяет включать и выключать Помощника.


    Терминальные свойства объекта Balloon

    С двумя свойствами этого объекта: Animation и Mode мы уже знакомы. Первое из них задает анимацию объекта Assistant при вызове его метода Show, в тот момент, когда показывается объект Balloon. Рассмотрим подробнее другие свойства
  • Heading - позволяет прочесть или установить строку, задающую текст заголовка.
  • Text - На вершине объекта Balloon сразу после заголовка может появляться некоторый текст. Его и устанавливает свойство Text. Оба элемента являются необязательными. Заметьте, что Heading и Text могут включать и графику. Последняя задается строкой, имеющей следующий синтаксис: { []}. Первый параметр задает тип графического файла (bmp или wmf), второй - его расположение, третий позволяет согласовать размер wmf-файла (Windows metafile), но не bmp-файла.
  • Icon - Слева от заголовка можно добавить специальный значок для привлечения внимания. Возможные значки задаются следующими константами: msoIconAlert, msoIconNone, или msoIconTip.
  • Button - Внутрь баллончика можно поместить некоторое количество кнопок заранее предустановленного типа. К ним, конечно, относятся кнопки: Ok, Cancel, Next, Close, Retry и им подобные. Значениями свойства могут быть константы, задающие соответствующее подмножество кнопок.

  • Это не все свойства объекта Balloon. Есть еще несколько важных для понимания свойств, но мы расскажем о них чуть позднее. А сейчас небольшой пример конструирования объекта Balloon с некоторыми из рассмотренных свойств:
    Листинг 7.3.
    (html, txt)
    Вот как выглядит это диалоговое окно баллончика с картинками, вставленными в текст:
    Терминальные свойства объекта Balloon

    увеличить изображение
    Рис. 7.3.  Объект Balloon с заданными свойствами
    Заметьте, следует быть внимательным при задании свойства Text, включающего картинки. При наличии ошибок диалоговое окно не будет открыто, но сообщений об ошибке не появится.

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

    в реестре можно воспользоваться Редактором

    Для регистрации chm-файла в реестре можно воспользоваться Редактором реестра (regedit.exe). Файл регистрируется в разделе: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\HTML Help

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

    в реестре можно воспользоваться Редактором

    Рис. 9.7.  Регистрация в реестре Windows chm-файла

    Давайте займемся еще одной проблемой, возникающей при отображении разделов справочного руководства в стандартном окне справки. Как я уже говорил, Office Assistant позволяет в ответ на вопрос пользователя показать в своем окне разделы, которые могут быть взяты как из стандартного, так и пользовательского справочного руководства. И те, и другие разделы могут отображаться в стандартном окне справки. Вкладки стандартного окна "Мастер Ответов" и "Указатель" также позволяют задавать вопросы и указывать индексы, относящиеся к обоим справочным руководствам. Фактически, таких руководств может быть несколько. Взгляните, как выглядит работа с указателем в этом окне:

    в реестре можно воспользоваться Редактором

    Рис. 9.8.  Отображение разделов стандартного и пользовательского руководств по заданному индексу

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

    Но мы отвлеклись, и так и не сформулировали суть проблемы. А она связана с вкладкой "Содержание". Дело в том, что нельзя объединить содержание двух справочных руководств - стандартного и пользовательского. Так какое же содержание должно быть показано? Однозначного ответа нет. Все зависит от выбора разработчика, который может управлять этим процессом на этапе создания chm-файла. В предыдущей лекции я рассказывал об определении окна справки, задании его свойств, о том, что задание вкладок "Избранное" и "Поиск" осуществляется включением флажков, задающих соответствующие свойства окна, в котором будет отображаться справочное руководство. Я не стал там заострять внимание на возможных проблемах, связанных с просмотром справочного руководства в окне справки. Теперь пришла пора сказать об этом. Для того чтобы просматривать стандартное оглавление в окне справки, при его определении необходимо выполнить следующие условия:

  • В качестве имени окна задать одно из двух возможных стандартных имен: MSO_Small или MSO_Large.
  • Изменить идентификатор окна на ненулевое значение. Эту операцию, к сожалению, приходится выполнять вручную, используя любой тестовый редактор, например, "Блокнот". Числовой идентификатор окна является последней константой, следующей за большим числом запятых в определении окна. Его значение обычно равно единице.
  • Убедиться в том, что имя "окна по умолчанию" (Default windows) совпадает с выбранным стандартным именем.



  • Вот как выглядит редактирование проекта HTML Help Workshop (hhp-файла) в простом текстовом редакторе "Блокнот":

    в реестре можно воспользоваться Редактором

    Рис. 9.9.  Редактирование hhp-проекта в окне текстового редактора "Блокнот"

    Для удобства восприятия рисунка я разорвал строку определения окна, что позволило увидеть весь текст этой строки. Фактически, определение окна задается одной строкой.

    В том случае, когда окну справки дано собственное, а не стандартное имя, при работе Office Assistant вкладка "Содержание" стандартного окна справки будет показывать оглавление пользовательского справочного руководства.

    Подведем теперь некоторые итоги и суммируем те факты, которые необходимо учитывать для обеспечения гладкого взаимодействия между различными инструментальными средствами - HTML Help Workshop, Answer Wizard Builder, Office Assistant:

  • Не использовать русских имен для файлов, используемых при построении справочного руководства (файлы разделов, chm-файл и другие файлы).
  • Задать нужным образом имя окна в определении hhp-проекта, понимая, что имя влияет на показ оглавления в стандартном окне справки.
  • Зарегистрировать chm-файл в реестре Windows. Без этого разделы справочного руководства не будут отображаться в стандартном окне справки.
  • Тщательно протестировать aw-файл, созданный средствами AWB. Разумно подобранное множество вопросов к разделам может значительно улучшить результаты его работы.


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

    Нам осталось рассмотреть в деталях заключительный шаг в обеспечении взаимодействия Office Assistant и Answer Wizard, чем мы сейчас и займемся.


    Для регистрации chm-файла в реестре можно воспользоваться Редактором реестра (regedit.exe). Файл регистрируется в разделе: HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\HTML Help

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

    в реестре можно воспользоваться Редактором

    Рис. 9.7.  Регистрация в реестре Windows chm-файла

    Давайте займемся еще одной проблемой, возникающей при отображении разделов справочного руководства в стандартном окне справки. Как я уже говорил, Office Assistant позволяет в ответ на вопрос пользователя показать в своем окне разделы, которые могут быть взяты как из стандартного, так и пользовательского справочного руководства. И те, и другие разделы могут отображаться в стандартном окне справки. Вкладки стандартного окна "Мастер Ответов" и "Указатель" также позволяют задавать вопросы и указывать индексы, относящиеся к обоим справочным руководствам. Фактически, таких руководств может быть несколько. Взгляните, как выглядит работа с указателем в этом окне:

    в реестре можно воспользоваться Редактором

    Рис. 9.8.  Отображение разделов стандартного и пользовательского руководств по заданному индексу

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

    Но мы отвлеклись, и так и не сформулировали суть проблемы. А она связана с вкладкой "Содержание". Дело в том, что нельзя объединить содержание двух справочных руководств - стандартного и пользовательского. Так какое же содержание должно быть показано? Однозначного ответа нет. Все зависит от выбора разработчика, который может управлять этим процессом на этапе создания chm-файла. В предыдущей лекции я рассказывал об определении окна справки, задании его свойств, о том, что задание вкладок "Избранное" и "Поиск" осуществляется включением флажков, задающих соответствующие свойства окна, в котором будет отображаться справочное руководство. Я не стал там заострять внимание на возможных проблемах, связанных с просмотром справочного руководства в окне справки. Теперь пришла пора сказать об этом. Для того чтобы просматривать стандартное оглавление в окне справки, при его определении необходимо выполнить следующие условия:

  • В качестве имени окна задать одно из двух возможных стандартных имен: MSO_Small или MSO_Large.
  • Изменить идентификатор окна на ненулевое значение. Эту операцию, к сожалению, приходится выполнять вручную, используя любой тестовый редактор, например, "Блокнот". Числовой идентификатор окна является последней константой, следующей за большим числом запятых в определении окна. Его значение обычно равно единице.
  • Убедиться в том, что имя "окна по умолчанию" (Default windows) совпадает с выбранным стандартным именем.



  • Вот как выглядит редактирование проекта HTML Help Workshop (hhp-файла) в простом текстовом редакторе "Блокнот":

    в реестре можно воспользоваться Редактором

    Рис. 9.9.  Редактирование hhp-проекта в окне текстового редактора "Блокнот"

    Для удобства восприятия рисунка я разорвал строку определения окна, что позволило увидеть весь текст этой строки. Фактически, определение окна задается одной строкой.

    В том случае, когда окну справки дано собственное, а не стандартное имя, при работе Office Assistant вкладка "Содержание" стандартного окна справки будет показывать оглавление пользовательского справочного руководства.

    Подведем теперь некоторые итоги и суммируем те факты, которые необходимо учитывать для обеспечения гладкого взаимодействия между различными инструментальными средствами - HTML Help Workshop, Answer Wizard Builder, Office Assistant:

  • Не использовать русских имен для файлов, используемых при построении справочного руководства (файлы разделов, chm-файл и другие файлы).
  • Задать нужным образом имя окна в определении hhp-проекта, понимая, что имя влияет на показ оглавления в стандартном окне справки.
  • Зарегистрировать chm-файл в реестре Windows. Без этого разделы справочного руководства не будут отображаться в стандартном окне справки.
  • Тщательно протестировать aw-файл, созданный средствами AWB. Разумно подобранное множество вопросов к разделам может значительно улучшить результаты его работы.


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

    Нам осталось рассмотреть в деталях заключительный шаг в обеспечении взаимодействия Office Assistant и Answer Wizard, чем мы сейчас и займемся.

    Answer Wizard и Office Assistant. Сложности

    Говоря о принципиальных сложностях, давайте начнем с Мастера Ответов - инструмента AWB. Работать с этим инструментом технически довольно просто - у него совсем немного команд. Значительно труднее понять, насколько хорошо Мастер Ответов справляется со своей работой. Напомню, что его задачей является индексация файлов разделов справочного руководства. Принципиальная сложность состоит в том, что Мастеру необходимо работать с русским языком при анализе текста разделов. Русский язык входит в число тех языков, с которыми умеет работать Мастер Ответов. Но индексация - поиск ключевых слов - это довольно тонкий процесс, здесь необходима сложная лингвистическая поддержка русского языка. На данном этапе такой поддержкой Мастер Ответов не обладает. Хотя он и справляется со своей задачей и обеспечивает построение индексов, иногда довольно удачно, но, в целом, вряд ли можно признать его работу удовлетворительной. Замечу, что при построении индексов у AWB нет даже такого простого средства, как "стоп-список", который используется при организации полнотекстового поиска в справочном руководстве. Напомню, что в этот список включаются союзы, предлоги и другие общеупотребительные слова, которые нецелесообразно использоваться в качестве индексов. К сожалению, при построении индексов у Мастера Ответов таких ограничений нет и можно обнаружить в процессе тестирования, что на вопросы "В" или "Как" будут выдан список почти всех разделов справочного руководства. Эта трудность носит принципиальный характер и преодолена она будет только тогда, когда в Мастере Ответов будет реализована более мощная поддержка естественных языков.
    Частично проблемы могут быть решены за счет возможности добавления вопросов к разделам. Удачно подобранные вопросы, принятые Мастером, гарантируют, что при подобных вопросах пользователя ему будут выданы действительно подходящие разделы справочного руководства. К сожалению, Мастер Ответов не все вопросы принимает, и понять его логику мне не удалось. Отмечу лишь некоторые экспериментально зафиксированные факты. Мастер Ответов не принимает в качестве вопросов символы латинского алфавита, что совершенно правильно по соображениям, о которых я уже говорил. Большинство символов русского алфавита, к сожалению, принимаются в качестве вопросов. Мастер Ответов может принять или отвергнуть вопрос в зависимости не только от самого вопроса, но и в зависимости от контекста - других вопросов и текста разделов. Поэтому в разных множествах вопросов одни и те же вопросы могут быть приняты в одном множестве и отвергнуты в другом.
    Некоторой рекомендацией может быть использование в текстах разделов справочного руководства и в вопросах, их сопровождающих, ключевых слов на английском языке. С английским Мастер Ответов справляется значительно лучше, чем с русским. По понятным причинам этой рекомендации не всегда можно следовать в пользовательских справочных руководствах, ориентированных на русскоязычного читателя.
    Еще одна принципиальная сложность связана с окном справки, в котором отображаются разделы справочного руководства. В предыдущей лекции было показано, как можно в приложение добавить собственный пункт меню или командную кнопку, выбор которых приводит к открытию окна, в котором отображается справочное руководство. При этом в открывающемся окне могут быть разные вкладки, позволяющие искать разделы по оглавлению, по указателям, использовать полно текстовый поиск или использовать папку "Избранное". Когда вопросы задаются в окне Office Assistant, возникает другая ситуация, - разделы справки будут отображаться в стандартном окне справки, открываемом Помощником. И это окно, определенное Помощником, а не окно, спроектированное при разработке справочного руководства. В этом стандартном окне, открываемом Помощником, три вкладки - "Содержание", "Мастер Ответов" и "Указатель". Как видите, вкладка "Поиск" отсутствует, она заменена вкладкой "Мастер Ответов", так что вместо полнотекстового поиска ответы формируются Мастером Ответов. В стандартном окне нет и папки "Избранное".
    То, что Помощник отображает справочное руководство в своем окне справки, не является ограничением. Достоинство предлагаемого подхода состоит в том, чтобы, сохраняя возможность получения стандартных справок по приложениям Office 2000, одновременно иметь возможность получать справки по терминам, специфичным для данного документа, отражающим его пользовательскую направленность. Поэтому в окне справки должны отображаться как разделы пользовательского, так и стандартного справочного руководства. Хотя окно справки является "слугой двух господ", свой внешний вид оно сохраняет постоянным, ориентированным на стандартное справочное руководство Office 2000. Конечно же, наряду с возможностью использования Office Assistant можно иметь и команду меню, позволяющую открыть пользовательское справочное руководство в собственном окне справки.
    А теперь прошу обратить особое внимание на одно важное требование, без соблюдения которого разделы пользовательского руководства не будут отображаться в стандартном окне справки. Скомпилированный chm-файл пользовательского справочного руководства должен быть зарегистрирован в реестре Windows. Без этого его разделы не будут отображаться в стандартном окне справки.
    Регистрация не требуется при открытии справочного руководства в собственном окне.

    Answer Wizard и Office Assistant. Взаимодействие

    Итак, будем полагать, что, используя инструментарий HHW, справочное руководство успешно создано, построен и зарегистрирован скомпилированный chm-файл. Будем полагать также, что инструментарий AWB позволил создать индексируемый aw-файл ответов на возможные вопросы пользователей, желающих получить нужную им справку. Как присоединить полученный aw-файл к Office Assistant, чтобы пользователь приложения Office 2000 мог задавать вопросы на естественном языке, работая с привычным для него Помощником?
    Прежде всего, напомню, что когда пользователь формирует свой вопрос в окне Помощника, то для ответа на этот вопрос Office Assistant вызывает Answer Wizard и именно Мастер Ответов, используя имеющиеся в его распоряжении aw-файлы, формирует названия разделов справочных руководств (chm-файлов), в которых может содержаться нужная справка. Переходя от такого общего понимания к пониманию на объектном уровне, замечу, что в лекции, посвященной общим объектам Office 2000 , уже рассматривался объект Answer Wizard. Одним из основных свойств этого объекта является свойство Files, возвращающее коллекцию aw-файлов, с которыми работает Мастер Ответов. Поэтому все, что нужно сделать для того, чтобы Office Assistant мог должным образом реагировать на вопросы пользователя, - это добавить нужный aw-файл в коллекцию файлов объекта Answer Wizard. Приведем текст соответствующей процедуры, решающей эту задачу:
    Public Sub HelpOfHelp() 'Создание объекта Answer Wizard 'добавление файлов ответа к справочной системе Dim i As Integer Dim customAnswerWizard As AnswerWizard Set customAnswerWizard = Application.AnswerWizard With customAnswerWizard 'Восстановление стандартного множества aw-файлов .ResetFileList 'Показать Помощника Assistant.Visible = True Debug.Print .Files.Count 'Отключение стандартных файлов в тех случаях, 'когда используется только собственное справочное руководство '.ClearFileList 'Добавление собственного справочного руководства .Files.Add ("e:\O2000\Book2\Cd\Help1\HelpOfHelp.aw") 'Отладочная печать aw-файлов For i = 1 To .Files.Count Debug.Print .Files(i) Next i Debug.Print .Files.Count End With End Sub

    Итак, будем полагать, что, используя инструментарий HHW, справочное руководство успешно создано, построен и зарегистрирован скомпилированный chm-файл. Будем полагать также, что инструментарий AWB позволил создать индексируемый aw-файл ответов на возможные вопросы пользователей, желающих получить нужную им справку. Как присоединить полученный aw-файл к Office Assistant, чтобы пользователь приложения Office 2000 мог задавать вопросы на естественном языке, работая с привычным для него Помощником?
    Прежде всего, напомню, что когда пользователь формирует свой вопрос в окне Помощника, то для ответа на этот вопрос Office Assistant вызывает Answer Wizard и именно Мастер Ответов, используя имеющиеся в его распоряжении aw-файлы, формирует названия разделов справочных руководств (chm-файлов), в которых может содержаться нужная справка. Переходя от такого общего понимания к пониманию на объектном уровне, замечу, что в лекции, посвященной общим объектам Office 2000 , уже рассматривался объект Answer Wizard. Одним из основных свойств этого объекта является свойство Files, возвращающее коллекцию aw-файлов, с которыми работает Мастер Ответов. Поэтому все, что нужно сделать для того, чтобы Office Assistant мог должным образом реагировать на вопросы пользователя, - это добавить нужный aw-файл в коллекцию файлов объекта Answer Wizard. Приведем текст соответствующей процедуры, решающей эту задачу:
    Public Sub HelpOfHelp() 'Создание объекта Answer Wizard 'добавление файлов ответа к справочной системе Dim i As Integer Dim customAnswerWizard As AnswerWizard Set customAnswerWizard = Application.AnswerWizard With customAnswerWizard 'Восстановление стандартного множества aw-файлов .ResetFileList 'Показать Помощника Assistant.Visible = True Debug.Print .Files.Count 'Отключение стандартных файлов в тех случаях, 'когда используется только собственное справочное руководство '.ClearFileList 'Добавление собственного справочного руководства .Files.Add ("e:\O2000\Book2\Cd\Help1\HelpOfHelp.aw") 'Отладочная печать aw-файлов For i = 1 To .Files.Count Debug.Print .Files(i) Next i Debug.Print .Files.Count End With End Sub


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

    Несколько комментариев к этой процедуре. Я ввел пользовательский объект customAnswerWizard, но можно было бы пользоваться и стандартным объектом. Реально существует только один объект AnswerWizard и все изменения отражаются на этом объекте. Метод ResetFileList восстанавливает список стандартных файлов, присущих объекту AnswerWizard. Метод ClearFileList делает этот список пустым, он используется перед добавлением собственных aw-файлов, например, для того, чтобы можно было работать только с этими файлами. Заметьте, метод ClearFileList в процедуре закомментирован. Это позволяет одновременно работать со стандартным и собственным справочным руководством. Вот как выглядит запрос на справку, сформулированный в Office Assistant:

    Answer Wizard и Office Assistant. Взаимодействие

    увеличить изображение
    Рис. 9.10.  Вопрос и ответ в Office Assistant

    Как видите, Мастер Ответов на вопрос "Help" указал разделы, как нашего справочного руководства, так и стандартного. Заметьте, в стандартном руководстве используются русские имена разделов. При создании собственного руководства мне не удалось справиться с решением этой задачи. И в заключение рассказа взгляните еще на один экран, на котором одновременно показаны два окна справки - стандартное окно и собственное окно. В обоих окнах отображается наше справочное руководство. Окна имеют разные закладки, одно из них открыто Office Assistant, другое - я открыл сам, нажав специально спроектированную командную кнопку.

    Answer Wizard и Office Assistant. Взаимодействие

    увеличить изображение
    Рис. 9.11.  Справочное руководство, открытое в двух окнах

    На этом и завершим рассказ о том, как Office Assistant и Answer Wizard обеспечивают работу с пользовательским справочным руководством. Еще один последний штрих к рассказу. Я проверил, что и в Excel вызов той же процедуры HelpOfHelp обеспечивает работу со справочным руководством из Office Assistant также хорошо, как и в приложении Word.


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

    Несколько комментариев к этой процедуре. Я ввел пользовательский объект customAnswerWizard, но можно было бы пользоваться и стандартным объектом. Реально существует только один объект AnswerWizard и все изменения отражаются на этом объекте. Метод ResetFileList восстанавливает список стандартных файлов, присущих объекту AnswerWizard. Метод ClearFileList делает этот список пустым, он используется перед добавлением собственных aw-файлов, например, для того, чтобы можно было работать только с этими файлами. Заметьте, метод ClearFileList в процедуре закомментирован. Это позволяет одновременно работать со стандартным и собственным справочным руководством. Вот как выглядит запрос на справку, сформулированный в Office Assistant:

    Answer Wizard и Office Assistant. Взаимодействие

    увеличить изображение
    Рис. 9.10.  Вопрос и ответ в Office Assistant

    Как видите, Мастер Ответов на вопрос "Help" указал разделы, как нашего справочного руководства, так и стандартного. Заметьте, в стандартном руководстве используются русские имена разделов. При создании собственного руководства мне не удалось справиться с решением этой задачи. И в заключение рассказа взгляните еще на один экран, на котором одновременно показаны два окна справки - стандартное окно и собственное окно. В обоих окнах отображается наше справочное руководство. Окна имеют разные закладки, одно из них открыто Office Assistant, другое - я открыл сам, нажав специально спроектированную командную кнопку.

    Answer Wizard и Office Assistant. Взаимодействие

    увеличить изображение
    Рис. 9.11.  Справочное руководство, открытое в двух окнах

    На этом и завершим рассказ о том, как Office Assistant и Answer Wizard обеспечивают работу с пользовательским справочным руководством. Еще один последний штрих к рассказу. Я проверил, что и в Excel вызов той же процедуры HelpOfHelp обеспечивает работу со справочным руководством из Office Assistant также хорошо, как и в приложении Word.

    Формы в Excel, Word и Power Point

    В Word, Excel и Power Point формы представляются объектом UserForm, принадлежащим общей библиотеке MSForms, являющейся частью объектной модели этих приложений. В отличие от близкого по духу объекта Form в Access, объект UserForm не имеет ни свойства HelpFile ни свойства HelpContextID. Правда, на этапе проектирования для формы можно задать свойство HelpContextID. Это же свойство можно задать на этапе проектирования и для элементов управления, размещаемых в форме. С объектной точки зрения объект UserForm обладает коллекцией Controls, а каждый элемент этой коллекции - объект Control - обладает свойством HelpContextID.
    Как уже говорилось ранее, с формой в этих приложениях нельзя связать отдельный файл справочного руководства. Единое справочное руководство связывается со всем программным проектом. Свойство HelpFile можно установить на этапе проектирования или программно, используя объект VBProject, входящий в общую библиотеку VBIDE. Этот объект имеет и свойство HelpContextID, которое используется в Object Browser, когда необходимо получить справку о данном проекте. О том, как работает клавиша F1 в формах, я написал выше.

    Идентификаторы и разделы справки. Построение отображения

    Ранее, когда речь шла о создании справочного руководства и работы с ним, не возникала необходимость использования идентификаторов разделов, поскольку в нужном месте открывалась вся справочная система, и поиск раздела осуществлялся с помощью специальных средств - оглавления, указателей и других. Но в ряде случаев, как, например, при получении контекстной справки, необходимо сразу же открывать нужный раздел справочной системы. В этом случае без знания идентификаторов разделов не обойтись. Иногда нужно знать символьный идентификатор, иногда числовой, как, например, при работе с элементами интерфейса формы.
    Для того чтобы разделы справочного руководства связать с числовыми и символьными идентификаторами, при работе в HHW требуется установить два отображения. Первое отображение задается в специальной секции MAP, оно устанавливает соответствие между двумя видами идентификаторов. Второе отображение задается в секции ALIAS, - оно связывает символьные идентификаторы с именами файлов, хранящих разделы справки. В совокупности оба отображения позволяют, зная любой из идентификаторов, найти нужный раздел справки.
    Поскольку одной из наших целей является включение в справочное руководство специальных контекстных справок, то мы также рассмотрим создание в hhp-проекте секции TEXT POPUPS, в которой задается ссылка на текстовый файл, содержащий контекстные справки и устанавливается соответствие между этими справками и их идентификаторами.
    Давайте перейдем к рассмотрению подробностей построения каждого из упомянутых отображений и формирования секций hhp-проекта: MAP, ALIAS, TEXT POPUPS.
    Отображение между двумя видами идентификаторов - символьными и числовыми - представляется в виде определения последовательности именованных констант. Синтаксис заимствован из языка С и каждая строка в определении отображения имеет вид:
    #define <символьный идентификатор> <числовой идентификатор>
    Вот как выглядит отображение, задаваемое для нашего примера:
    #define IDH_Man 1000 #define IDH_Wolf 1001 #define IDH_Goat 1002 #define IDH_Cabbage 1003 #define IDH_Boat 1004 #define IDH_River 1005 #define IDH_LeftBank 1006 #define IDH_RightBank 1007 #define IDH_Shark 1008 #define IDH_Island 1009

    Ранее, когда речь шла о создании справочного руководства и работы с ним, не возникала необходимость использования идентификаторов разделов, поскольку в нужном месте открывалась вся справочная система, и поиск раздела осуществлялся с помощью специальных средств - оглавления, указателей и других. Но в ряде случаев, как, например, при получении контекстной справки, необходимо сразу же открывать нужный раздел справочной системы. В этом случае без знания идентификаторов разделов не обойтись. Иногда нужно знать символьный идентификатор, иногда числовой, как, например, при работе с элементами интерфейса формы.
    Для того чтобы разделы справочного руководства связать с числовыми и символьными идентификаторами, при работе в HHW требуется установить два отображения. Первое отображение задается в специальной секции MAP, оно устанавливает соответствие между двумя видами идентификаторов. Второе отображение задается в секции ALIAS, - оно связывает символьные идентификаторы с именами файлов, хранящих разделы справки. В совокупности оба отображения позволяют, зная любой из идентификаторов, найти нужный раздел справки.
    Поскольку одной из наших целей является включение в справочное руководство специальных контекстных справок, то мы также рассмотрим создание в hhp-проекте секции TEXT POPUPS, в которой задается ссылка на текстовый файл, содержащий контекстные справки и устанавливается соответствие между этими справками и их идентификаторами.
    Давайте перейдем к рассмотрению подробностей построения каждого из упомянутых отображений и формирования секций hhp-проекта: MAP, ALIAS, TEXT POPUPS.
    Отображение между двумя видами идентификаторов - символьными и числовыми - представляется в виде определения последовательности именованных констант. Синтаксис заимствован из языка С и каждая строка в определении отображения имеет вид:
    #define <символьный идентификатор> <числовой идентификатор>
    Вот как выглядит отображение, задаваемое для нашего примера:
    #define IDH_Man 1000 #define IDH_Wolf 1001 #define IDH_Goat 1002 #define IDH_Cabbage 1003 #define IDH_Boat 1004 #define IDH_River 1005 #define IDH_LeftBank 1006 #define IDH_RightBank 1007 #define IDH_Shark 1008 #define IDH_Island 1009

    Заметьте, символьные идентификаторы начинаются префиксом IDH_. В этом случае система HHW опознает их как символьные идентификаторы и осуществляет весьма полезные проверки корректности задания отображения между идентификаторами и разделами справки. Так что рекомендуется всегда использовать этот префикс в символьных именах. Это отображение строится в обычном текстовом редакторе внутри HHW, в Блокноте или в Word, если Вы привыкли там работать. Файл сохраняется как текстовый файл с уточнением ".h". Это уточнение типично для заголовочных файлов языка C. По сути, файл таковым и является, он является частью hhp-проекта и хранится в каталоге проекта вместе с остальными файлами. Теперь несколько слов о том, как создается секция MAP.

    При работе с вкладкой Project в главном окне среды HHW на левой инструментальной панели расположена специальная кнопка
    Идентификаторы и разделы справки. Построение отображения
    HTML Help API Information. Она называется так, потому что API функция HTML Help, которая явно или неявно используется при вызове разделов справочной системы, требует в качестве информации (параметров) задания числовых или символьных идентификаторов. Но об этом мы еще поговорим позже.

    Щелчок по данной кнопке открывает диалоговое окно, три вкладки которого обеспечивают создание трех секций hhp-проекта, о которых идет речь. Вот как выглядит это окно, открытое на вкладке MAP:

    Идентификаторы и разделы справки. Построение отображения

    Рис. 9.13.  Создание секции MAP

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

    Заметьте, символьные идентификаторы начинаются префиксом IDH_. В этом случае система HHW опознает их как символьные идентификаторы и осуществляет весьма полезные проверки корректности задания отображения между идентификаторами и разделами справки. Так что рекомендуется всегда использовать этот префикс в символьных именах. Это отображение строится в обычном текстовом редакторе внутри HHW, в Блокноте или в Word, если Вы привыкли там работать. Файл сохраняется как текстовый файл с уточнением ".h". Это уточнение типично для заголовочных файлов языка C. По сути, файл таковым и является, он является частью hhp-проекта и хранится в каталоге проекта вместе с остальными файлами. Теперь несколько слов о том, как создается секция MAP.

    При работе с вкладкой Project в главном окне среды HHW на левой инструментальной панели расположена специальная кнопка
    Идентификаторы и разделы справки. Построение отображения
    HTML Help API Information. Она называется так, потому что API функция HTML Help, которая явно или неявно используется при вызове разделов справочной системы, требует в качестве информации (параметров) задания числовых или символьных идентификаторов. Но об этом мы еще поговорим позже.

    Щелчок по данной кнопке открывает диалоговое окно, три вкладки которого обеспечивают создание трех секций hhp-проекта, о которых идет речь. Вот как выглядит это окно, открытое на вкладке MAP:

    Идентификаторы и разделы справки. Построение отображения

    Рис. 9.13.  Создание секции MAP

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

    Идентификаторы и разделы справки. Построение отображения

    Рис. 9.14.  Первое окно открытия файла

    В этом окне уже появилась кнопка Browse. Нажмем ее.

    Идентификаторы и разделы справки. Построение отображения

    Рис. 9.15.  Второе окно открытия файла

    Теперь прошу обратить особое внимание на два факта. Первый - в качестве шаблона для поиска файлов указан шаблон "*.h", Второй факт - в открытой папке HelpToWGC не найдено ни одного файла с таким уточнением. Могу Вас заверить, что заголовочные файлы с уточнением h присутствуют в этом каталоге. Прежде чем обсуждать данные факты, продолжим работу. Естественно я ввел известное мне имя заголовочного файла в поле имени и нажал кнопку "Открыть". В результате я получил ответ, что найти файл в каталоге не удалось:

    Идентификаторы и разделы справки. Построение отображения

    Рис. 9.16.  Третье окно открытия файла

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

    Идентификаторы и разделы справки. Построение отображения

    Рис. 9.17.  Четвертое окно открытия файла

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

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


    Несколько слов о том, почему не виден файл с уточнением ".h". Он и не должен быть виден, поскольку это текстовый файл, и он будет отображаться, когда шаблоном будет служить уточнение ".txt". Другое дело, что в грамотной реализации нужно вначале найти все текстовые файлы и отобрать среди них те файлы, которые имеют уточнение h. Подводя итог, следует сказать, что хотя формально секция MAP была создана, но фактически процесс ее создания потерпел неудачу и файл, указанный в этой секции не будет виден системой. Выход из этой ситуации есть, но об этом чуть позже. Аналогичная ситуация с заголовочными файлами возникает и при работе с двумя другими вкладками в процессе создания секций ALIAS и TEXT POPUPS.

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

    Символьный идентификатор> = <Имя файла>

    В нашем примере этот заголовочный файл выглядит следующим образом:

    IDH_Man = Man.htm IDH_Wolf = Wolf.htm IDH_Goat = Goat.htm IDH_Cabbage = Cabbage.htm IDH_River = River.htm IDH_LeftBank = LeftBank.htm IDH_RightBank = RightBank.htm IDH_Shark = Shark.htm IDH_Boat = Boat.htm IDH_Island = Island.htm

    Перейдем теперь к формированию секции ALIAS. При выборе одноименной вкладки в отличие от вкладки MAP появляются две возможности создания секции.

    Идентификаторы и разделы справки. Построение отображения

    Рис. 9.18.  Создание секции ALIAS

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

    Нажатие кнопки INCLUDE позволяет создать секцию ALIAS за один шаг, используя подготовленный в текстовом редакторе заголовочный файл. В этом случае открывается уже знакомое окно "Include File" (рис. 14) и повторяется процесс, так подробно описанный выше.

    Использование функции API - HtmlHelp

    Как и другие функции API, функция HtmlHelp написана на языке C и ее "родное" описание имеет следующий синтаксис:
  • HWND HtmlHelp(HWND hwndCaller, LPCSTR pszFile, UINT uCommand, DWORD dwData) Параметры этой функции имеют следующий смысл:
  • hwndCaller - задает описатель окна, вызываемого HtmlHelp, в котором будет появляться справка.
  • pszFile - указывает HTML-файл, URL или chm-файл. За именем файла может следовать определение окна, отделенное от имени знаком ">".
  • uCommand - задает команду, определяющую действие, выполняемое данной функцией.
  • dwData - указывает данные, которые требуются для выполнения той или иной команды.

  • Список возможных команд велик. С двумя основными командами мы познакомились при тестировании chm-файла, создаваемого в среде HHW. Этими командами являются HH_DISPLAY_TOPIC и HH_HELP_CONTEXT. Обе команды отображают в окне справки HTML-файл, но пользуются разной информацией. Для первой команды параметр pszFile задает имя chm-файла, а параметр dwData должен указывать на файл с разделом справки, являющийся частью скомпилированного chm-файла. Команда HH_HELP_CONTEXT позволяет отобразить в окне справки HTML-файл, заданный числовым идентификатором. Параметр dwData в этом случае задает значение HelpContextID.
    Функция HtmlHelp входит в состав библиотеки, определяющей HTML Help ActiveX элемент управления (HHCtrl.ocx). Она реализует некоторые функциональные возможности этого элемента. Я напомню, что этот ActiveX элемент входит в состав HTML Help Workshop.
    Для того, чтобы эту функцию можно было вызывать в программном коде VBA, необходимо предварительно объявить ее в операторе DECLARE. Необходимо объявить также используемые константы, а в общем случае и типы данных. Основная проблема при этом состоит в том, чтобы правильно отобразить типы языка C на типы языка VB. Приведу два возможных варианта объявления функции HtmlHelp на VBA, которые использовались мной в экспериментах:
    Public Const Myf = "e:\O2000\DsCd\HelpToWGC\WhatThisHelpToWGC.chm" 'Константы, необходимые для вызова API функции HtmlHelp Public Const HH_DISPLAY_TOPIC = &H0 'Вызов по имени раздела Public Const HH_HELP_CONTEXT = &HF 'Вызов по Context ID 'Описание API функции HtmlHelp Public Declare Function HtmlHelp Lib "hhctrl.ocx" _ Alias "HtmlHelpA" _ (ByVal hwndCaller As Long, _ ByVal pszFile As String, _ ByVal uCommand As Long, _ dwData As Any) As Long 'Описание варианта API функции HtmlHelp Public Declare Function HtmlHelpLongArg Lib "hhctrl.ocx" _ Alias "HtmlHelpA" (ByVal hwndCaller As Long, _ ByVal pszFile As String, ByVal uCommand As Long, _ ByVal dwData As Long) As Long

    Обратите внимание, в первом варианте тип параметра dwData описан как Any. В этом случае при вызове не проверяется тип передаваемых данных, и вся ответственность за корректность вызова лежит на программисте. Это описание функции является универсальным и позволяет вызывать любые возможные команды, доступные для функции HtmlHelp. Во втором варианте описания функции тип параметра dwData зафиксирован. Этот вариант может быть использован при вызове команды HH_HELP_CONTEXT, когда в качестве значения передается числовой идентификатор.

    Для иллюстрации возможностей работы с функцией API я использовал предыдущий пример, заменив вызовы метода Help на вызов функции HtmlHelp:

    Private Sub ToggleButton1_Click() 'Выдача контекстной справки - работает!!! 'Глобальный параметр Myf задает путь к файлу справочного руководства 'Второй параметр метода Help позволяет по HelpContextID определить нужный раздел

    If Me.ToggleButton1.Value Then 'Call Application.Help(Myf, Me.ToggleButton1.HelpContextID) 'Вызов API функции HtmlHelp Call HtmlHelpLongArg(0, Myf, HH_HELP_CONTEXT, Me.ToggleButton1.HelpContextID) End If End Sub

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

    Использование функции API - HtmlHelp

    Рис. 9.27.  Вызов контекстной справки, использующий API функцию HtmlHelp

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

    Как сделать так, чтобы все работало правильно

    Я построил hhp-проект, в секциях MAP и ALIAS которого использовались заголовочные файлы. Но тестирование этого проекта показало, что заголовочные файлы реально не видны и нужное отображение не строится. Взгляните, как выглядит сообщение, полученное мной при тестировании одного из примеров:
    Как сделать так, чтобы все работало правильно

    Рис. 9.19.  "Неуспех" при тестировании проекта с заголовочными файлами
    Я напомню, для тестирования проекта после его компиляции из пункта меню Test главного меню системы HHW следует выбрать команду HTML Help API. В открывшемся окне из списка Command следует поочередно выбрать и протестировать две команды: HH_DISPLAY_TOPIC и HH_HELP_CONTEXT. Первая из них отображает разделы, при задании имени файла, содержащего данный раздел. С этой командой особых трудностей обычно не возникает. Чтобы успешно работала вторая команда, где задается числовой идентификатор, требуется успешное создание раздела MAP. В нашем примере успеха нет и причина этого нам известна - заголовочные файлы не работают так, как нужно.
    Как справиться с возникшей проблемой? На самом деле это не сложно. Вот что нужно сделать:
  • Открыть hhp-файл проекта в текстовом редакторе "Блокнот".
  • Руками выполнить операцию Include, копируя соответствующие тексты заголовочных файлов и явно подставляя их в разделы MAP и ALIAS вместо предложения Include.
  • Сохранить скорректированный файл.
  • Открыть файл проекта в HHW и заново скомпилировать chm-файл.
  • Повторить тестирование, чтобы убедиться в работоспособности проекта.

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

    Рис. 9.20.  Успешное получение раздела справки по его числовому идентификатору
    То, что, в конечном счете, удается успешно создать числовые и символьные идентификаторы разделов, имеет крайне важно, поскольку эти идентификаторы широко используются при выдаче контекстных справок в самых разных ситуациях, о которых мы еще поговорим. А теперь пора перейти к проблеме, успешное решение которой мне так и не удалось найти. Но прежде хочу обратить Ваше внимание еще на одну ошибку, которая может возникать в процессе работы с hhp-проектом. Если возникает необходимость провести редактирование разделов MAP и ALIAS, то делать это нужно в текстовом редакторе "Блокнот". Попытка провести редактирование этих разделов в HHW приводит к непоправимой ошибке и снятию приложения.

    Компоненты проекта и справки

    Я напомню, что в окне просмотра объектов - браузере объектов (Object Browser) можно увидеть структуру каждого объекта, так или иначе составляющего документ Office 2000. Еще в момент возникновения документа строится его каркас на основе совокупности библиотек объектов, составляющих по сути Office 2000. В зависимости от типа документа и выбора пользователя в каркас документа включаются те или иные библиотеки. Объекты, включенные в эти библиотеки, являются стандартными, и в окне просмотра объектов можно увидеть не только структуру объекта - свойства, методы, события, константы, но и получить подробную справку, как о самом объекте, так и о его элементах. Когда с документом работает программист, то он достраивает каркас документа, создавая полноценное строение. Работа программиста, прежде всего, сводится к созданию программного проекта, который является неотъемлемой частью документа. Компонентами программного проекта являются формы, стандартные модули, модули классов. Каждая форма, создаваемая программистом, имеет, тем не менее, множество стандартных свойств, методов и событий. Наряду с ними программист населяет форму элементами управления, и эти пользовательские объекты также становятся элементами формы. Модули создаются программистом с нуля. Модуль класса, описывающий пользовательский класс объектов, состоит из свойств (переменных), методов (процедур), событий и констант. Стандартный модуль состоит из констант, переменных и процедур.
    Структура программного проекта и всех его компонент доступна для просмотра в окне браузера. Проект является такой же библиотекой, как и остальные библиотеки, составляющие документ. Имя этой библиотеки совпадает с именем проекта. По умолчанию, если проекту не дается собственное имя, используется имя "Project". Если из списка библиотек, выбрать имя библиотеки, задающей проект, то в окне просмотра откроется структура проекта, все его компоненты. Возникает вопрос, можно ли получать справку о компонентах проекта - формах модулях проекта, о компонентах каждого модуля? Можно ли, например, получать справку о свойствах, методах и событиях классов, созданных программистом? Ответ на эти вопросы положителен, хотя и с некоторыми оговорками и сетованиями на то, что не все работает так, как хотелось бы.
    Я напомню, что с программным проектом можно связать только одно справочное руководство, поэтому разделы справки для всех элементов проекта должны быть собраны в этом руководстве. Если такое руководство создано, - построен chm-файл, содержащий секции MAP и ALIAS, то для получения справок работает техника числовых идентификаторов - HelpContextID, уже подробно описанная для других ситуаций. Взгляните, как выглядит структура программного проекта с именем WGCProject:
    Компоненты проекта и справки

    увеличить изображение
    Рис. 9.28.  Структура программного проекта WGCProject
    Заметьте, в левом окне Classes показаны все компоненты проекта, в правом окне MembersOf для компоненты, выбранной в левом окне, показана ее структура, - все элементы, входящие в ее состав. На рисунке для выбранного в левом окне класса объектов с именем WGCClass, показаны все элементы этого класса - заданные для него свойства и методы. В нижнем окне для выбранной компоненты показано ее определение. Давайте рассмотрим, как задаются определения компонент и разделы справочного руководства, появляющиеся при запросе справки о той или иной компоненте. Первым делом, в окне свойств проекта необходимо задать:
  • chm-файл справочного руководства,
  • HelpContextID раздела, задающего справку ко всему проекту,
  • Определение проекта, которое будет появляться в нижнем окне браузера объектов.


  • Никаких проблем не возникает и для компонент проекта, показанных в окне классов, то есть для форм и классов. Для задания определения такой компоненты и связывания с ней раздела справки необходимо:

  • Щелкнуть в окне Classes правой кнопкой мыши на выбранной компоненте.
  • Выбрать из появившегося меню команду Properties.
  • В появившемся диалоговом окне Member Options указать значение HelpContextID и задать определение компоненты.
  • Щелкнуть "OK".


  • Вот как выглядит диалоговое окно Member Options при задании характеристик класса с именем WGCClass:

    Компоненты проекта и справки

    Рис. 9.29.  Задание определения и HelpContextID для компоненты программного проекта

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

    А теперь прошу обратить внимание, - аналогичная процедура задания определения и HelpContextID для элементов проекта более низкого уровня, заданных в правом окне MembersOf, в данной версии Office 2000 еще не работает. Поэтому, например, нельзя выдать контекстную справку о конкретном свойстве или методе пользовательского класса. Теоретически все должно работать аналогичным образом. Однако, если в этом окне на шаге 1 выбрать некоторое свойство, то на шаге 2 выбрать команду Properties не удастся, поскольку она недоступна (показана серым цветом). Для методов - команда доступна и появляется окно Member Options, в котором можно задать нужные характеристики. Однако они не запоминаются при нажатии кнопки "OK", так что заданные установки не вступают в силу. Можно, конечно, надеяться, что в последующих версиях все эти недоработки будут устранены.

    Контекстные справки к элементам интерфейса. Эксперимент

    Проделана вся необходимая работу, как на стороне программного проекта "Волк, Коза и Капуста", так и на стороне создания справочного руководства для этой игры. Я создал и зарегистрировал chm-файл, содержащий, как разделы обычного справочного руководства, так и контекстные справки к элементам интерфейса формы, представляющей игровое поле. На основе chm-файла был создан и Aw-файл, позволяющий Рокки отвечать на вопросы. Осталось провести эксперименты и посмотреть, что из всего этого работает. Прежде всего, я попытался получить контекстные справки к тем надписям на форме, для которых определены числовые идентификаторы и созданы соответствующие разделы в справочном руководстве:
    Контекстные справки к элементам интерфейса. Эксперимент

    Рис. 9.23.  Попытка получения контекстной справки к элементу интерфейса
    Как видите, выдается сообщение об отсутствии контекстной справки для этого элемента интерфейса. Хотя истинная причина состоит не в отсутствии раздела, а в том, что не задано нужное отображение числового идентификатора на символьный идентификатор и связанный с ним раздел, где содержится контекстная справка. Что же все-таки работает в данной ситуации? Если специализированный курсор со знаком вопроса не подводить к элементам интерфейса, для которых задан HelpContextID, а щелкнуть где-нибудь в произвольном месте формы, то откроется справочное руководство и можно будет получить справку обычным способом. Заметьте, что для этого не понадобилось создавать никаких дополнительных командных кнопок или команд меню. Это не совсем то, что хотелось бы, но вызывать справочное руководство в форме достаточно просто, что, в конечном счете, позволяет получить справки ко всем элементам интерфейса.
    Для получения контекстных справок в формах может использоваться и клавиша F1. В нашем примере она работает также как и специальный курсор со знаком вопроса, позволяя открыть справочное руководство, но открывающийся раздел всегда задается самой формой - ее HelpContextID и не зависит от того, какой элемент интерфейса был активным. Заметьте, нажатие клавиши F1 приводит к открытию справочного руководства и в том случае, если свойство формы WhatThisButton выключено.
    Справки может выдавать и Помощник.
    Контекстные справки к элементам интерфейса. Эксперимент

    Рис. 9.24.  Рокки отвечает на вопрос об основных героях игры
    Как видите, Рокки показал все темы, в которых описаны запрашиваемые визуальные объекты. Это еще один способ решения поставленной задачи. Способ справиться с задачей всегда существует. Надеюсь, правда, что в следующей версии HHW не нужно будет искать обходные пути и будет доступен прямой способ решения данной задачи.

    Проделана вся необходимая работу, как на стороне программного проекта "Волк, Коза и Капуста", так и на стороне создания справочного руководства для этой игры. Я создал и зарегистрировал chm-файл, содержащий, как разделы обычного справочного руководства, так и контекстные справки к элементам интерфейса формы, представляющей игровое поле. На основе chm-файла был создан и Aw-файл, позволяющий Рокки отвечать на вопросы. Осталось провести эксперименты и посмотреть, что из всего этого работает. Прежде всего, я попытался получить контекстные справки к тем надписям на форме, для которых определены числовые идентификаторы и созданы соответствующие разделы в справочном руководстве:
    Контекстные справки к элементам интерфейса. Эксперимент

    Рис. 9.23.  Попытка получения контекстной справки к элементу интерфейса
    Как видите, выдается сообщение об отсутствии контекстной справки для этого элемента интерфейса. Хотя истинная причина состоит не в отсутствии раздела, а в том, что не задано нужное отображение числового идентификатора на символьный идентификатор и связанный с ним раздел, где содержится контекстная справка. Что же все-таки работает в данной ситуации? Если специализированный курсор со знаком вопроса не подводить к элементам интерфейса, для которых задан HelpContextID, а щелкнуть где-нибудь в произвольном месте формы, то откроется справочное руководство и можно будет получить справку обычным способом. Заметьте, что для этого не понадобилось создавать никаких дополнительных командных кнопок или команд меню. Это не совсем то, что хотелось бы, но вызывать справочное руководство в форме достаточно просто, что, в конечном счете, позволяет получить справки ко всем элементам интерфейса.
    Для получения контекстных справок в формах может использоваться и клавиша F1. В нашем примере она работает также как и специальный курсор со знаком вопроса, позволяя открыть справочное руководство, но открывающийся раздел всегда задается самой формой - ее HelpContextID и не зависит от того, какой элемент интерфейса был активным. Заметьте, нажатие клавиши F1 приводит к открытию справочного руководства и в том случае, если свойство формы WhatThisButton выключено.
    Справки может выдавать и Помощник.
    Контекстные справки к элементам интерфейса. Эксперимент

    Рис. 9.24.  Рокки отвечает на вопрос об основных героях игры
    Как видите, Рокки показал все темы, в которых описаны запрашиваемые визуальные объекты. Это еще один способ решения поставленной задачи. Способ справиться с задачей всегда существует. Надеюсь, правда, что в следующей версии HHW не нужно будет искать обходные пути и будет доступен прямой способ решения данной задачи.

    Контекстные справки, появляющиеся во всплывающих окнах

    Инструментарий HHW позволяет создавать и включать в справочное руководство специальную секцию TEXT POPUPS, предназначенную для обеспечения работы с контекстными справками к элементам интерфейса и появление этих справок во всплывающих окнах. Созданию секции TEXT POPUPS, которая необходима при работе с такими контекстными справками, должно предшествовать создание двух текстовых файлов. Один из них уже может существовать, если создавалась секция MAP, - это заголовочный файл, задающий отображение между символьными и числовыми идентификаторами. Второй текстовый файл содержит описание контекстных сообщений, которые будут появляться во всплывающих окнах. Чуть позже я расскажу о синтаксисе, которому должен удовлетворять этот файл. После того, как эти два файла будут созданы, можно переходить к созданию секции TEXT POPUPS, для чего достаточно нажать одноименную вкладку.
    Контекстные справки, появляющиеся во всплывающих окнах

    Рис. 9.21.  Создание секции TEXT POPUPS
    Две командные кнопки "Header" и "Text File" позволяют включить в секцию два файла - заголовочный файл и файл с контекстными сообщениями, что и должно обеспечить скомпилированному chm-файлу возможность выдавать при запросах контекстные справки к элементам интерфейса. Существует небольшая, но досадная разница в том, как подключается заголовочный файл в секциях MAP, ALIAS и TEXT POPUPS. В двух первых случаях формируется предложение #include <имя файла>. В последнем случае вставляется непосредственно имя файла, что не позволяет обойти возникающую проблему "невидимости" заголовочного файла.

    Метод Help объектов Application

    Метод Help по-разному работает для разных приложений - в Word и Access он позволяет получать справки только по стандартным разделам. В приложениях Excel и Power Point метод Help позволяет получить справку к заданному разделу пользовательского справочного руководства.
    В приложении Word этот метод имеет следующий синтаксис:
    Help(HelpType)
    Параметр HelpType задается константой типа wdHelpType. Каждая константа определяет тот или иной вид справки. Вот небольшой пример:
    Public Sub CallHelp() 'Вызов стандартной справки Application.Help (wdHelpAbout) End Sub
    Синтаксис метода Help в приложениях Excel и Power Point следующий: Help(HelpFile, HelpContextID)
    Параметры этого метода нам хорошо знакомы. Тем самым, в этих приложениях становится доступна работа с пользовательским справочным руководством и его разделами. В конечном итоге, это позволяет организовать выдачу контекстных справок в обработчиках различных событий - по нажатию специальных кнопок, по нажатию клавиши F1 или любой другой специальной клавиши. Возможностей здесь много.
    Я рассмотрю сейчас пример, в котором метод Help будет использоваться для получения справок об элементах интерфейса формы. Это будет все та же форма с игрой "Волк, Коза и Капуста". Для того, чтобы она заработала в Excel, мне пришлось, конечно, выполнить операции экспорта - импорта формы и соответствующих модулей. Еще одно типичное изменение в таких случаях связано с заменой объекта ActiveDocument на ActiveWorkBook, определяющих соответственно активные документы Word и Excel.
    Моя цель состоит в том, чтобы получать контекстные справки к элементам интерфейса данной формы, используя возможности метода Help в Excel. Вот как я собирался достичь этой цели. Как Вы помните, в форме для каждого визуального объекта введен дополнительный объект - надпись (Label). Надписи полезны сами по себе, но введены они были и по той причине, что графические объекты не имеют важного свойства HelpContextID. Для надписей это свойство существует и на этапе проектирования его значение можно установить. В обработчике события Click для каждой надписи я предполагал вызывать метод Help, передавая ему в качестве параметра контекстный идентификатор соответствующей надписи. Так что все выглядело достаточно логично, - всякий раз, когда пользователь щелкает мышью по надписи, ему в ответ выдается справка о визуальном элементе, связанном с этой надписью. Вот как выглядит спроектированный мной обработчик события Click для надписи:

    Private Sub Label1_Click() 'Выдача контекстной справки - не работает!!! ' Глобальный параметр Myf задает путь к файлу справочного руководства 'Второй параметр метода Help позволяет по HelpContextID определить нужный раздел

    Call Application.Help(Myf, Me.Label1.HelpContextID)

    End Sub

    Однако мой хорошо продуманный план потерпел неудачу из-за очередного bug'а ("жучка"). Дело в том, что, хотя надписи и имеют свойство HelpContextID, значение которого можно установить на этапе проектирования, программно работать с этим свойством для надписей невозможно. Ни установить, ни прочесть значение этого свойства программно не удается. Ошибка возникает, как при явных попытках работать с этим свойством, так и при вызовах внутри процедуры. Поэтому в приведенном примере вызов метода Help терпит неудачу из-за невозможности получить значение свойства HelpContextID для надписи. Так что Вам повезло, - Вы узнали еще об одной ошибке. А мне не повезло, поскольку пришлось в очередной раз переделать интерфейс моей формы. Надеюсь, Вы понимаете, что найти выход из данной ситуации нетрудно, - я заменил надписи другими элементами интерфейса, для которых свойство HelpContextID корректно работает как на этапе проектирования, так и программно. Вместо надписей я использовал специальные утапливаемые кнопки - объекты класса ToggleButton. Для них также существует событие Click, так что наша схема по-прежнему работает, - изменился лишь тип элементов, нажатие которых приводит к выдаче контекстной справки. Я внес лишь небольшие усовершенствования и вот как выглядит обработчик события Click для кнопки ToggleButton:

    Private Sub ToggleButton1_Click() 'Выдача контекстной справки - работает!!! 'Глобальный параметр Myf задает путь к файлу справочного руководства 'Второй параметр метода Help позволяет по HelpContextID определить нужный раздел

    If Me.ToggleButton1.Value Then Call Application.Help(Myf, Me.ToggleButton1.HelpContextID) End If End Sub

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

    Метод Help объектов Application

    Рис. 9.25.  Форма для игры "Волк, Коза и Капуста", открытая в Excel

    Новые возможности при создании справочного руководства

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


  • О загрузке Answer Wizard Builder

    Поскольку у меня на компьютере установлен Office 2000 в редакции Developer, то у меня уже был инсталлирован Answer Wizard Builder. Мне осталось только запустить его на выполнение. Если у Вас нет версии Developer и нет Answer Wizard Builder, то Вы можете скачать этот инструмент с сервера Microsoft по адресу: http://www.microsoft.com\office\ork\2000\download\AWBuild.exe
    Инструментарий Answer Wizard Builder распространяется свободно, но работать на компьютере будет при условии, что установлен Office 2000.

    Объекты инструментальных панелей

    Наряду с формами, основными элементами интерфейса, создаваемого для документов Office 2000, являются пользовательские инструментальные панели. Как объект, инструментальная панель принадлежит классу CommandBar. Ее свойство Controls возвращает коллекцию класса CommandBarControls. На панелях располагаются элементы разного типа - меню и команды меню, кнопки нескольких возможных видов. Как объекты, эти элементы принадлежат следующим классам: CommandbarButton, CommandBarComboBox, CommandBarControl, CommandbarPopUp. Объекты всех этих классов обладают свойствами HelpFile и HelpContextID. Оба свойства должны быть заданы для каждого элемента. Заметьте, файл, задающий справку, не связывается ни с панелью, ни с документом, а указывается для каждого отдельного элемента. В принципе это дает некоторую свободу и позволяет, например, иметь отдельный файл для каждой инструментальной панели. Устанавливаются эти свойства программно. Вот пример установления этих свойств у первых трех элементов одной пользовательской инструментальной панели:
    Public Sub AddHelpProps() 'Добавление контекстных справок к элементам панели Dim panel As CommandBar Dim ctrl As CommandBarButton
    Dim HelpFilePath As String HelpFilePath = "e:\O2000\DsCd\WhatThisHelpToWGC\HelpToWGC.chm"
    Set panel = CommandBars("ButtonPanel") 'Устанавливаем свойства элементов панели 'Help Свойства кнопки Set ctrl = panel.Controls(1) ctrl.HelpFile = HelpFilePath ctrl.HelpContextId = 1000
    Set ctrl = panel.Controls(2) ctrl.HelpFile = HelpFilePath ctrl.HelpContextId = 1001
    Set ctrl = panel.Controls(3) ctrl.HelpFile = HelpFilePath ctrl.HelpContextId = 1002 End Sub


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

    Для каждой формы или отчета Access можно создать собственное справочное руководство. Можно, конечно, иметь одно руководство для всех объектов базы данных Access. Объекты Form и Report в Access обладают свойствами HelpFile и HelpContextID, которые можно установить на этапе проектирования или программно. Элементы управления, размещаемые в формах, обладают свойством HelpContextID, задающим числовой идентификатор раздела справки, что позволяет связать каждый элемент с соответствующим разделом справочного руководства. Работая с формой, пользователю достаточно нажать клавишу F1, чтобы получить контекстную справку к тому или иному элементу управления. Зная HelpContextID элемента управления, находящегося в фокусе ввода, и файл, связанный с формой, система отображает в окне справки нужный раздел справочного руководства. Если HelpContextID для активного элемента управления не задан, то отображается раздел, связанный с формой.

    Описание Answer Wizard Builder

    Описание Answer Wizard Builder

    увеличить изображение
    Рис. 9.1.  Мастер Ответов в момент открытия
    Мастер AWB наряду с индексным aw-файлом создает проект - файл с уточнением ".awb". Наличие проекта позволяет при необходимости возвращаться и корректировать уже созданные индексные файлы. Понятно, что такая необходимость может возникать при корректировке справочного руководства, добавлении или удалении разделов, существенного изменения их содержания.
    Как видно из рисунка, при создании нового проекта я использую chm-файл "HelpOfHelp". Заметьте, на этом этапе возможны альтернативы - во-первых, можно открыть существующий проект, взяв его за основу для нового проекта, во-вторых - можно при построении проекта использовать Web-узел.
    Сделаю два замечания. Возможно, внимательный читатель заметил, что я отказался от использования русского имени файла и использую новый chm-файл, в котором заменил имя "Справка о справке" на имя "HelpOfHelp". В процессе построения этого файла я заменил также имя файла "Содержание", содержащего оглавление, на имя "Contents". При работе с AWB нельзя использовать русские имена файлов из-за возникающих проблем. Еще одно замечание также может быть полезным. Изучая документацию MSDN на сервере Microsoft, я нашел уведомление об ошибке - в открывающемся окне, где задается имя скомпилированного chm - файла, уточнение "chm" следует набирать в нижнем регистре. В противном случае, если указать, например, - "MyHelp.Chm", появится сообщение о том, что файл с таким именем не найден, хотя он и существует и путь к нему будет указан правильно.
    Но вернемся к первому диалоговому окну инструмента AWB. При нажатии кнопки "OK" происходит автоматическая декомпиляция chm - файла и открывается следующее окно AWB:
    Описание Answer Wizard Builder

    увеличить изображение
    Рис. 9.2.  Основное окно Мастера Ответов
    В левом окошке показан весь список разделов справочного руководства. В этом списке можно выбрать один из разделов. Справа расположены два окошка - верхнее для задания заголовка раздела, выбранного в левом окне, нижнее - для задания списка вопросов, которые разработчик справочного руководства связывает с данным разделом. Этой работой я и занялся, задав для каждого из разделов свое множество вопросов. Конечно, меня больше интересовало не содержание вопросов, а такие проблемы, как возможность работы с русским языком, использование русских и английских терминов в одном вопросе, возможности редактирования и подобные детали. Замечу, что при создании текста вопросов все возможности по редактированию допустимы в полном объеме - копирование, вырезка и перенос вопросов из одного раздела в другой. Типичной является ситуация, когда одни и те же вопросы связываются с несколькими темами. Есть, правда, ограничение - один вопрос должен быть связан не более чем с пятью разделами. У каждого раздела может быть практически неограниченное число вопросов. Вот как выглядит множество вопросов, указанное мной для одного из разделов:
    Описание Answer Wizard Builder

    увеличить изображение
    Рис. 9.3.  Задание вопросов в AWB

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

    Я уже рассказывал в предыдущей лекции о том, как можно показать справочное руководство в ответ на запрос пользователя, нажавшего специально спроектированную командную кнопку или выбравшего команду меню, созданную для этой цели. Напомню, что для решения этой задачи в обработчике соответствующего события запускалась специальная программа HTML Help executable program (hh.exe), которой в качестве параметра передается имя chm-файла.
    Поговорим теперь еще о двух способах программного вызова справочной системы в принципе, позволяющих не только вызвать справочное руководство, но и открыть его на нужном разделе. Эти два способа основаны на использовании:
    Метода Help, которым обладают объекты Application приложений Office 2000.
    Функции API - HtmlHelp.

    Программные объекты

    Свойства HelpFile и HelpContextID имеют целый ряд программных объектов, начиная с главного программного объекта VBProject и кончая его компонентами - формами и модулями. Эти свойства имеет объект Err и метод Raise, используемые при обработке программных ошибок. Обладают этими свойствами и широко употребительные функции - MsgBox и InputBox. В конце этой лекции я чуть подробнее расскажу о том, как выдаются справки на разных этапах работы с программным проектом.

    Работа, выполняемая на стороне программного проекта

    Для того чтобы обеспечить возможность появления всплывающих подсказок к элементам интерфейса в формах, следует выполнить три условия:
  • Для каждого элемента интерфейса в форме, для которого желательно получать контекстную справку, необходимо задать значение свойства HelpContextID.
  • Для самой формы, помимо свойства HelpContextID, необходимо задать значение еще двух свойств - WhatThisButton и WhatThisHelp.
  • В свойствах программного проекта необходимо задать путь к справочному руководству, содержащему разделы справки, которые будут связаны с тем или иным элементом интерфейса.

  • Давайте поговорим обо всем этом подробнее. Прежде всего, обратите внимание, что справочное руководство связывается с проектом, а не с формой. Оно одно на весь проект, поэтому, сколько бы не было различных форм, все разделы справки должны быть собраны в одно руководство. Заметьте, также, что файл, содержащий руководство, может иметь расширение htm или chm в зависимости от того, каким инструментарием он подготовлен. Надеюсь, не надо объяснять, как нужно задавать свойства программного проекта. Ранее я уже об этом говорил.
    Булевы свойства WhatThisButton и WhatThisHelp формы должны быть установлены как true (истина). Включение кнопки WhatThisHelp обеспечивает поддержку выдачи контекстных справок. Если же дополнительно включена кнопка WhatThisButton, то в этом случае в правом углу заголовка формы появится дополнительная кнопка со знаком вопроса, и форма приобретет новое качество. Всякий раз, когда пользователь щелкнет эту кнопку, курсор изменит свой внешний вид - рядом с курсором будет плавать знак вопроса. Достаточно теперь подвести такой курсор к выбранному элементу интерфейса и щелкнуть по нему, как в появившемся всплывающем окне отобразится контекстная справка, поясняющая суть данного элемента.
    Понятно, что чудес не бывает и появляющийся раздел справки однозначно определяется числовым значением контекстного идентификатора - HelpContextID, установленного для каждого элемента интерфейса. Особых требований к этим идентификаторам не предъявляется, важно только, чтобы они были уникальными, позволяя для каждого элемента интерфейса во всем программном проекте однозначно установить нужный раздел справочного руководства. Возникает вопрос, - откуда берутся значения этих идентификаторов? Это вопрос о том, что было раньше - курица или яйцо. Если раньше строится справочное руководство, а уж потом контекстно-чувствительные справки добавляются к программному проекту, то значения идентификаторов определяются при построении справочного руководства. Чаще всего, возникает обратная ситуация, когда проектируется форма, задаются уникальные значения контекстных идентификаторов для каждого ее элемента, а потом уже строится справочное руководство, поддерживающее данные значения. В общем, правильный ответ состоит в том, что на этапе проектирования системы должны быть предусмотрены разделы справки для каждого элемента интерфейса и связанные с этими разделами два уникальных идентификатора - числовой (HelpContextID) и строковый, называемый символическим идентификатором раздела. О символических идентификаторах поговорим позже. Сейчас же замечу, что вся система контекстных справок, где бы они ни выдавались, - в формах или документах, построена на использовании этих двух видов идентификаторов. Скажу также, что и возникающие проблемы связаны с тем, что не всегда удается построить правильное отображение между идентификаторами и соответствующими разделами справки.
    В качестве примера формы с элементами интерфейса, для которых попытаемся построить контекстные справки, я взял уже знакомую форму из игры "Волк, Коза и Капуста". Я слегка модифицировал интерфейс этой формы и вот как он теперь выглядит:
    Работа, выполняемая на стороне программного проекта

    Рис. 9.12.  "Новый" интерфейс в игре "Волк, Коза и Капуста"
    Наша форма была достаточно специфичной, - все ее элементы интерфейса были рисунками, в ней отсутствовали типичные для форм элементы - поля ввода, списки, кнопки. К каждому рисунку я добавил подпись, вдвое увеличив тем самым число элементов интерфейса. Добавление подписей позволяет мне обратить Ваше внимание на два важных момента. Во-первых, рисунки не имеют свойства HelpContextID и поэтому для них не возможна контекстная справка. Так что добавление подписей является в данном случае вынужденной мерой, позволяя получить справку по подписи к рисунку. Во-вторых, и в тех случаях, когда элемент интерфейса, например, поле ввода, имеет HelpContextID и может реагировать на пользовательский запрос, целесообразно эту же справку связывать и с подписью, сопровождающей этот элемент. Дело в том, что пользователь часто предпочитает щелкнуть по подписи, чтобы получить справку, а не на сам элемент интерфейса. Щелкнуть по подписи кажется более естественным и более безопасным.
    Итак, подведу некоторые итоги. У меня есть программный проект, в нем есть форма, в ней элементы интерфейса и я выполнил все три условия, необходимые для организации контекстной справки типа "Что это такое?". В свойствах элементов интерфейса (в данном случае - это одни подписи, что не играет принципиальной роли) я задал значения HelpContextID от 1000 до 1009. Для рисунков, напомню, задать идентификатор для получения контекстной справки, к сожалению, невозможно. Можно было бы задать идентификатор для самой формы, но я этого не сделал, поскольку в нашей форме из-за визуальных объектов не осталось "живого" места. На следующем шаге я изменил значения вышеупомянутых булевых свойств формы и, как можно видеть на рисунке, у формы появился знак вопроса в правом верхнем углу. Я установил в свойствах программного проекта имя файла справочного руководства, которое должно содержать разделы справки для каждого элемента интерфейса. Этот файлу я дал длинное имя "WhatThisHelpToWGC.chm" и его предстоит создать уже знакомыми средствами инструментария HTML Help Workshop. Этим мы и займемся.


    В этой лекции мы продолжим разговор о создании справочной системы. В предыдущей лекции подробно рассматривались вопросы создания справочного руководства, используя возможности инструментария HTML Help Workshop. Но тема на этом не исчерпана. Прежде всего, следует понимать, что при разработке полноценной справочной системы, как правило, создается не одно, а несколько справочных руководств, каждое из которых имеет свое предназначение. Одно из них может быть справочным руководством по тому Решению, которое разработчик предлагает пользователю. Именно такое справочное руководство имелось в виду при рассмотрении материалов предыдущей лекции. Другие справочные руководства создаются для работы с контекстными справками. Например, для выдачи контекстных справок к элементам интерфейса офисных документов или объектам программного проекта может создаваться специальное справочное руководство. Еще одна важная тема, которую нельзя обойти при рассмотрении вопросов создания справочной системы, связана с Мастером Ответов (Answer Wizard), одним из основных участников процесса получения справок в среде Office 2000.

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

    Создание текстового файла с контекстными справками

    Контекстные справки, появляющиеся во всплывающих окнах, как правило, представляют собой тексты небольшого размера. Поэтому в отличие от обычных разделов справочного руководства, каждый из которых содержится в отдельном файле, все такие контекстные справки могут быть собраны в одном файле. Иногда предпочтительнее вместо одного большого файла иметь несколько файлов, например, отдельные файлы для элементов каждой формы. Такой сборный файл с контекстными справками в HHW создается в текстовом, а не в HTML формате. Структура такого файла достаточно проста и удовлетворяет следующему синтаксису:
    <Заголовок раздела> <Текст контекстной справки> [ <Заголовок раздела> <Текст контекстной справки>]…
    Файл, как видите, представляет последовательность озаглавленных разделов. Заголовок раздела начинается специальным ключевым словом и содержит символьный идентификатор раздела. Вот его синтаксис:
    .topic <Символьный идентификатор>
    Текст контекстной справки представляет собой обычный текст без картинок и прочих украшений, возможных для текстов в формате HTML, используемых в разделах справочного руководства. Вот два начальных раздела из файла, созданного для очередного примера:
    .topic IDH_Man Вот Человек, который следит за всеми зверьми. Он не допустит, чтобы волк съел козу, не разрешит козе съесть капусту. Он перевезет всех с левого берега на правый берег реки. Он помнит, что лодка его мала и не выдержит трех пассажиров. .topic IDH_Wolf Это волк, который дружен с человеком, равнодушен к капусте, но обязательно съест козу, если рядом нет человека.
    В hhp-проект, создаваемый для игры "Волк, Коза и Капуста", были включены, как HTML-файлы с разделами справочного руководства, так и текстовый файл, содержащий контекстные справки к элементам интерфейса, подготовленные для показа во всплывающих окнах. Соответственно проект содержит все три секции - MAP, ALIAS и POPUPS. Заменить в секции POPUPS заголовочный файл на его содержание не удается, по причинам, о которых я уже говорил. В результате файл компилируется с ошибками и, хотя они носят характер предупреждений, нужное отображение между идентификаторами и разделами контекстной справки так и не строится. Вот как выглядит результат трансляции проекта:
    Создание текстового файла с контекстными справками

    Рис. 9.22.  Результат компиляции hhp-проекта игры "Волк, Коза и Капуста quot;
    С другой стороны стоит отметить, что при тестировании проекта обе команды HtmlHelp API функции работают успешно, что уже само по себе обеспечивает возможность организовать получение контекстных справок, используя вызовы функции HtmlHelp.
    На этом я завершаю описание двух важных вопросов, возникающих при создании chm-файлов. В конечном итоге, мы научились связывать символьные и числовые идентификаторы с разделами справочного руководства. Я подробно описал, как можно построить сборный текстовый файл, содержащий контекстные справки, и как добавить секцию POPUPS в справочное руководство, но, к сожалению, мои усилия по разрешению проблем, возникающих на этом этапе, не увенчались успехом.

    Справки и программный проект

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


  • Справки к элементам интерфейса

    Важной составляющей профессионально создаваемых систем является предоставление пользователю возможности получить "на лету" справку ко всем элементам интерфейса. Для чего предназначена та или иная команда меню? Что произойдет при нажатии этой командной кнопки? Каков формат данных, вводимых в это поле ввода? На все подобные вопросы пользователь должен иметь возможность получить немедленную справку, не роясь в недрах справочного руководства. Такие специальные справки к элементам интерфейса называются справками типа "Что это такое?". Они представляют контекстно-чувствительные справочные сообщения, появляющиеся во всплывающих (pop up) окнах, когда, например, специальный курсор подводится к тому или иному элементу интерфейса или выбирается пункт меню из контекстного меню, открываемого при нажатии правой кнопки на элементе интерфейса, или нажимается клавиша F1, когда элемент интерфейса находится в фокусе ввода.
    Тема организации подобных справок достаточно обширна, прежде всего, из-за разнообразия интерфейса. Одно дело организовать справку к элементам интерфейса внутри формы, скажем к командной кнопке, посаженной в форму. Другое дело выдать справку к той же командной кнопке, посаженной непосредственно в тот или иной документ Office 2000. Организация справок к командам меню и кнопкам, расположенным на инструментальных панелях, также имеет свою специфику. Свою специфику имеет и организация справок в формах Access.
    Я начну с подробного рассмотрения одного вопроса - создания контекстных справок для элементов интерфейса внутри формы. Во-первых, это, пожалуй, наиболее важный случай, во-вторых, организация справок для других ситуаций имеет много общего. Но есть и еще одна серьезная причина, она заключается в том, что мне не удалось полностью решить поставленную задачу. Полагаю, что рассказ о возникающих проблемах может быть не менее интересен для тех, кто будет решать аналогичную задачу. Тем более что часть из этих проблем мне удалось преодолеть, хотя и не в полном объеме. К проблемам я был готов, поскольку в Руководстве программиста по VBA Office 2000 сказано, что для организации контекстных справок следует использовать предыдущий вариант инструментария справочной системы, используя WinHelp, а не HTML Help. С другой стороны, в описании системы HTML Help Workshop такого предупреждения нет. В ее справочной системе рассказывается, как создавать в справочном руководства специальный раздел, обеспечивающий работу с контекстными справками. С этим стоило разобраться. Но обо всем по порядку.

    Справки об ошибках периода выполнения программного кода

    Когда выполняется программный код, возможно возникновение ошибок, причины которых могут быть самыми разными. Это могут быть ошибки программиста, создающего код. Но часто причиной ошибок является нарушение спецификаций, гарантирующих корректную работу программы. Поэтому во многих ситуациях, когда уже ошибка возникла, очень важно выдать справку, объясняющую возможные причины ошибки. Понятно, что только стандартными справками в таких ситуациях не обойтись. Например, если ошибка возникает при работе объекта класса, созданного программистом, то именно он и должен позаботиться о выдаче корректных пользовательских справок, объясняющих природу ошибки. Для этой цели можно использовать те возможности, которые предоставляет объект Err и метод Raise, для которых можно указать имя chm-файла и HelpContextID соответствующего раздела.
    Объект Err содержит информацию о последней ошибке выполнения. Этот объект создается вместе с проектом и имеет глобальную область определения. При возникновении ошибки заполняются свойства этого объекта и, тем самым, определяется имя chm-файла и HelpContextID данной ошибки. Затем эти свойства могут быть обработаны подходящим образом. Чаще всего, вызывается функция MsgBox, имеющая кнопку Help, нажатие которой приводит к выдаче контекстной справки. Метод Raise служит для возбуждения собственных ошибок, когда в результате анализа обнаружена исключительная ситуация, при которой невозможно нормальное выполнение программы. Метод Raise возбуждает ошибку, передавая нужные параметры объекту Err, в том числе имя chm-файла и HelpContextID собственной ошибки.
    На этом я завершу изложение этой большой и важной темы, посвященной созданию справочной системы для документов Office 2000.

    Справки типа "Что это такое?" к элементам интерфейса в формах

    Работа по организации справок данного типа состоит из двух частей. Часть работы связана с программным проектом, формой, элементами интерфейса данной формы. Нужно уметь корректно задать нужные свойства при проектировании всех этих элементов, для того чтобы появились подобные справки. Вторая часть этой работы состоит в подготовке разделов справки, и она выполняется специальным инструментарием, например, Winhelp или HHW.

    в разных приложениях Office 2000

    Подведем некоторые итоги и посмотрим, как в разных приложениях Office 2000 может использоваться справочное руководство. Во многих случаях получение справок основано на технике, использующей числовые идентификаторы, связанные с разделами справочного руководства. С объектной точки зрения многие объекты, так или иначе, входящие в состав Office 2000, обладают свойствами HelpFile и HelpContextID, определяющими файл, где хранится справочное руководство и числовой идентификатор, однозначно определяющий раздел внутри этого руководства. Файл справочного руководства может быть создан различными инструментальными средствами - HTML Help Workshop или WinHelp, и может быть либо chm-файлом, либо hlp-файлом. Давайте разберемся, какие объекты Office 2000 обладают свойствами HelpFile и HelpContextID и как они используются для выдачи справок.

    Вопросы на естественном языке и Мастер Ответов

    Пользователи Office 2000 уже привыкли, что всякий раз, когда возникает необходимость в получении справки, у них под рукой Помощник - Office Assistant, которому можно задать вопрос в форме, принятой для естественных языков, например, на русском языке и получить список разделов справочной системы, где наиболее вероятно содержится ответ на поставленный вопрос. Следует сказать, что за спиной Помощника стоит Мастер Ответов (Answer Wizard), который фактически и готовит ответы на вопросы, заданные Помощнику. Хотелось бы иметь такую же возможность и при работе с собственным справочным руководством. И такая возможность есть.
    В состав Office 2000 Developer входит специальный инструмент, называемый Answer Wizard Builder. С его помощью можно подготовить специальные файлы с уточнением ".aw", которые и использует Помощник при подготовке ответов на вопросы. Инструмент Answer Wizard Builder (AWB) в своей работе использует уже созданное справочное руководство. Поскольку, как мы знаем, есть две основные возможности представления справочного руководства - в виде откомпилированного chm - файла или Web-узла, то Мастеру Ответов также приходится ориентироваться в своей работе на оба возможных случая.
    В чем суть работы Мастера Ответов? Она - в автоматическом индексировании разделов справочной системы. Мастер строит индексный файл, позволяющий установить соответствие между словами, встречающимися в вопросе, и словами, используемыми в тексте разделов. Анализируя текст каждого раздела, Мастер создает систему индексов (ключевых слов), характеризующих данный раздел. Когда в вопросе, заданном в Помощнике, встречается ключевое слово, выбранное Мастером в качестве индекса некоторого раздела, то при ответе Помощника данный раздел войдет в число разделов, рекомендуемых пользователю.
    Процесс анализа содержимого разделов и построения индексов идет автоматически без участия разработчика справочной системы. Но, чтобы не оставить его совсем в стороне и дать ему возможность повлиять на процесс создания индексов, в состав Мастера включены дополнительные средства. С каждым разделом справочного руководства разработчик может связать серию вопросов. Эти вопросы в определенной мере предвосхищают возможные вопросы, задаваемые конечными пользователями при работе со справочной системой. Мастер Ответов использует эти вопросы при создании индексного файла. Если множество вопросов подобрано удачно, то есть шанс, что вопросы пользователя будут близки к этому множеству и, следовательно, пользователю будет рекомендован действительно нужный ему раздел. В данном контексте "близость" означает совпадение используемых терминов в пользовательских вопросах и вопросах, заранее заготовленных.
    Давайте посмотрим, как это все работает на самом деле. Что работает, а что работает не совсем так, как хотелось бы. В предыдущей лекции я описал процесс создания справочного руководства "Справка о справке". Поэтому у меня имелся соответствующий chm - файл, с которым и будет работать Мастер Ответов при построении индексного aw - файла.

    

        Труд: Организация - Управление - Персонал