В чем же специфика офисного программирования, чем оно отличается от программирования в программной среде таких языков как VB,
Заметьте, что MacroRecorder помимо своей основной функции играет и важную обучающую роль. Во многих случаях, когда я затруднялся понять, с какими объектами мне следует работать для решения той или иной задачи, я включал MacroRecorder, решал задачу вручную, а затем анализировал текст полученного макроса, при необходимости занимался его оптимизацией, доводя его до нужной кондиции.
К сожалению, Microsoft все в меньшей степени поддерживает MacroRecorder в своих новых разработках.
Подведем теперь некоторые итоги и постараемся ответить на два главных вопроса:
Итак, давайте рассмотрим, что может дать офисное программирование пользователям Office. В свое время, обращаясь к пользователям и рассказывая им о преимуществах офисного программирования, я, стараясь сделать свою мысль о важности настройки документов Office образной, формулировал ее следующим образом:
"Работать с документами Office 2000, не используя VBA, все равно, что играть на ненастроенном пианино. Играть, конечно, можно, но профессионал этого позволить себе не может".
Используя это сравнение, хотелось обратить внимание пользователей на важность, а главное, на возможность настройки документов на решение специфических задач пользователя. Программирование офисных документов это одно из основных достоинств Office и грешно не использовать такую возможность. Документ, "сшитый по заказу" (custom
Однако, мое сравнение, как и всякое сравнение "хромает". Его можно интерпретировать и по-другому: "Фирма Microsoft предлагает своим пользователям ненастроенные документы ("расстроенное пианино"). Чтобы с ними работать, нужно их предварительно настраивать". Дабы избежать подобных заключений, несправедливых по отношению к этому прекрасному продукту, я теперь предпочитаю в подобных ситуациях говорить так:
"Работать с документами Office 2000 все равно, что играть на хорошо настроенном пианино. Но у этого инструмента есть возможности, открывающиеся для профессионалов. Нажмите ALT + F11 и создайте собственную композицию".
Вот только некоторые из тех преимуществ, что получает конечный пользователь, использующий программируемые офисные документы:
А есть ли преимущества у программиста, работающего в Office? Есть, и их, пожалуй, не меньше, чем у пользователя:
Заключая этот параграф, хочется еще раз напомнить пользователям и программистам, что именно их совместная работа над офисными документами может дать наилучшие результаты. В этом состоит одна из главных особенностей офисного программирования.
Давайте перейдем от лозунгов и призывов к конкретным делам. Для начала рассмотрим создание документа - обложки. Как мы говорили ранее, это некоторый специальный вид офисных документов, в которых не требуется знания объектов Office 2000. Такой документ представляет программный проект под обложкой одного из офисных документов, причем не важно какую из обложек выбрать - Word, Excel или, например, Power Point. Создается такой документ почти также как и программный проект в любой визуальной среде. По этой причине он достаточно легко переносится в любую из этих сред. Типичным примером таких документов, как мы говорили, являются игры. Рассмотрим посему реализацию хорошо известной игры "Волк, Коза и Капуста". Для понимания реализации нужно знать основы VBA и работу с интерфейсными объектами формами и их элементами.
Всем известна задача о "Волке, козе и капусте". Вот ее краткая формулировка. Человеку необходимо без потерь перевести с левого на правый берег реки волка, козу и капусту. При этом необходимо выполнять следующие четыре правила:
В этой игре нет ничего, что потребовало бы привлечения специальных возможностей Office 2000, достаточно знать VB и интерфейсные объекты. Реализация этой игры может служить хорошим примером создания документа - обложки, то есть такого документа Office 2000, под обложкой которого находится обычный программный проект, написанный в ядре языка VBA, по существу на чистом VB, и не использующий обращений к специальным объектам приложений Office 2000. С другой стороны, реализация этой игры позволяет продемонстрировать создание и работу с визуальными объектами в документах Office 2000. Зачастую, крайне важно иметь видимые образы для используемых объектов документа и разрешать пользователю документа работать с этими образами, производя над ними те или иные манипуляции.
В нашей игре есть 8 основных объектов: Человек, Волк, Коза, Капуста, Река, Левый Берег, Правый Берег, Лодка. Я добавил еще один объект - Акулу, чтобы реку было страшнее переплывать. Эти объекты будут иметь видимые образы, манипулируя которыми игрок добивается достижения поставленной цели, доставить человека, козу, волка и капусту с левого на правый берег. Полем, где будет разворачиваться действие игры, будет обычная форма VBA, она будет образом объекта Река. На реке (форме) я размещу образы других объектов игры. Каждый из этих объектов будет задаваться объектом VBA класса Image из библиотеки классов MSForms.
Пользователь (игрок) будет действовать в этом видимом мире объектов. Для того чтобы посадить путника в лодку, он может щелкнуть по нему левой кнопкой мыши или перетащить путника в лодку. Для того чтобы лодка переправилась на другой берег, опять-таки можно щелкнуть по ней или явно перетащить ее на другой берег при нажатой левой кнопке мыши. Наконец, чтобы высадить всех пассажиров на берег, достаточно щелкнуть по берегу левой кнопкой. С другой стороны, можно высаживать пассажиров по одному, явно перетаскивая их из лодки на берег. Таким образом, пользователь будет являться причиной событий, возникающих в мире объектов нашей игры. В ответ на происходящие события будут вызываться обработчики этих событий.
Перед программистом, реализующим эту игру, стоят две основные задачи:
Первую из этих задач он, скорее всего, как это сделал я, будет решать руками, визуально проектируя интерфейс, для решения второй задачи ему потребуется знание языка VBA и интерфейсных объектов. Знание объектов Office 2000 (объектов Word, Excel, Power Point) ему не понадобится, под какой бы документной обложкой не был реализован этот программный проект.
Сделаем еще одно замечание, касающееся будущей реализации. Поскольку все визуальные объекты игры размещены в одной форме, то и обработчики событий, происходящих с объектами формы, будут размещены в модуле, связанном с этой формой. Поэтому вся реализация игры может быть сосредоточена в одном модуле, модуле, связанном с формой. Я, однако, поступлю в соответствии с собственными рекомендациями, согласно которым, правильная технология состоит в том, чтобы к модулю формы добавить еще и стандартный модуль, в котором реально происходит обработка событий, большинство же обработчиков, находящихся в модуле формы, строятся очень простыми и состоят из вызова соответствующей процедуры стандартного модуля.
Теперь, когда стратегия реализации намечена, можно приступить к ее исполнению. В тот момент, когда я пишу эти строки, форма с игрой находится перед моими глазами, Полагаю, и Вам будет легче понимать мои рассуждения, если Вы будете представлять, как выглядит поле действия и основные объекты игры. Взгляните на соответствующий рисунок:
(рис 6.1) Форма в начальном состоянии в игре "Волк, Коза и Капуста"
Моя цель состоит в проектировании интерфейса нашей игры. Поскольку стратегическое решение уже принято, действие игры будет разворачиваться в диалоговом окне - форме VBA, то и начинать нужно с создания формы. Вот краткий отчет о моих действиях:
Height и Width. Я изменил имя формы, назвав ее WGCForm, и дал заголовок "Wolf, Goat and Cabbage". Главное, я изменил свойство BackColor, выбрав из палитры цвет, подходящий для речной глади. Теперь форма стала похожей на реку. Еще одно серьезное изменение свойств состояло в том, что форму я сделал немодальной, изменив свойство ShowModal. Форма VBA является, как известно, обычным диалоговым окном. Всякое такое окно может быть модальным или немодальным. Из модальных окон нельзя выйти, не закончив диалога. Если же форму сделать немодальной, то это важное свойство формы позволит выходить из нее, не завершив диалога, зани
маться другими делами, а потом возвращаться в форму. В частности, это позволяет во время игры при необходимости читать документ Word или создавать его, как это делаю я, имея форму перед глазами.Boat, и связал с ним соответствующую картинку лодки. Еще один объект Shark с картинкой акулы я разместил в реке. Он введен больше для антуража и как будущий возможный герой при расширении игры. Вместе с тем он будет играть и некоторую полезную роль, позволяя в любой момент начать игру с начала.Picture так, чтобы соответствующая картинка соответствовала образу объекта. Благо с этим теперь особых проблем нет, так как галерея ClipArt, поставляемая с Office 2000, содержит тысячи различных картинок. Проблема состоит лишь в том, чтобы из этого изобилия выбрать подходящую картинку. Напомню, что поиск в галерее ClipArt возможен по ключевому слову. Задайте, например, "goats" для поиска картинки с изображением козы, учитывая, что при задании ключевого слова лучше использовать множественное число. Итак, я нашел картинки, подогнал их под выбранные размеры Image-элементов, установив подходящее значение свойства PictureSizeMode. Мои объекты получили соответствующие имена: Man, Wolf, Goat and Cabbage.Обратите внимание, порядок размещения Image-элементов имеет значение, поскольку при их частичном совмещении один из них (более поздний) перекрывает другой. Поскольку такая ситуация будет иметь место, когда герои игры собираются в лодке, то целесообразно вначале на форме разместить лодку, а изображение человека разместить последним. Результат моих действий показан на предыдущем рисунке. При некотором навыке, когда картинки под рукой, все действие по формированию интерфейса занимает 10 -15 минут. У меня на это ушло больше времени, так как из-за отсутствия способностей к рисованию проектирование интерфейса это не самая сильная моя сторона. И здесь как раз тот самый случай, когда такие задачи должен решать специалист - дизайнер, а не программист.
Теперь, когда проектирование интерфейса закончено, можно приступать ко второй, более интересной части нашей задачи, собственно к программированию нашей игры.
На этом этапе нам придется решить целый ряд небольших задач, возникающих по ходу игры. Вот перечисление некоторых этапов, позволяющее структурировать игру и наши действия по ее реализации:
Будем следовать этой схеме при описании реализации игры.
При проектировании интерфейса были введены образы объектов, с которыми может взаимодействовать конечный пользователь. Теперь пришла пора ввести сами объекты. Обычно это одна из самых серьезных задач, требующая, как правило, введения собственных классов, тщательного продумывания свойств, методов и событий, которыми должны обладать объекты класса. Мы же можем обойтись без особых сложностей, поскольку у нас довольно простая задача. Наши объекты, подчиняясь игроку, выполняют лишь некоторые перемещения, число которых ограниченно. Задача программы следить лишь за тем, чтобы эти перемещения соответствовали небольшому числу правил. Заметьте, что это игра человека с самим собой, а не игра человека с компьютером. Нам нет необходимости в реализации какой-либо
Для реализации игры достаточно ввести 5 переменных, следящих за состоянием перемещаемых объектов: человека, волка, козы, капусты и лодки. Каждый из них может находиться в трех состояниях, быть на левом берегу, правом берегу или в лодке (у объекта Boat всего два состояния). Еще одна переменная будет следить за числом пассажиров в лодке. Вот описания из раздела Declarations стандартного модуля с именем WGCModule, который я создал первым делом:
Option Explicit 'Состояния наших объектов - человека, волка, козы, капусты и лодки Public StateOfMan As String Public StateOfWolf As String Public StateOfGoat As String Public StateOfCabbage As String Public StateOfBoat As String Public CountInBoat As Byte 'Число пассажиров в лодке Public WidthOfRiver 'Ширина реки, используемая при переправе
Приведем теперь процедуру InitialStates, которая задает начальные установки для наших переменных и визуальных объектов:
Public Sub InitialStates() 'Задаем начальные состояния объектов и их образов StateOfMan = "LeftBank" StateOfWolf = "LeftBank" StateOfGoat = "LeftBank" StateOfCabbage = "LeftBank" StateOfBoat = "LeftBank" CountInBoat = 0 With WGCForm .Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10 .Man.Visible = True .Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10 .Wolf.Visible = True .Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10 .Goat.Visible = True .Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10 .Cabbage.Visible = True .Boat.Top = .LeftBank.Top + 130: .Boat.Left = .LeftBank.Left + _ .LeftBank.Width .Boat.Visible = True WidthOfRiver = .Width - .LeftBank.Width - .RightBank.Width _ - .Boat.Width End With End Sub
Процедура InitialStates будет вызываться в ответ на такие события, как инициализация или активация нашей формы:
Private Sub UserForm_Activate() InitialStates End Sub Private Sub UserForm_Initialize() InitialStates End Sub
Рассмотрим вначале самый простой способ посадки пассажира в лодку. Щелчок левой кнопки мыши на видимом объекте образе одного из наших героев будет перемещать его (образ) в лодку. При этом, естественно, для того, чтобы перемещение было допустимым, нужно чтобы герой и лодка находились на одном берегу. Программно перемещение объекта в лодку делается чрезвычайно просто, для этого достаточно изменить свойства Top и Left, дав им новые значения, зависящие от значения этих свойств объекта Boat. Приведем соответствующие обработчики события Click для каждого из героев игры:
Private Sub Man_Click() ManInBoat 'Call IntoBoat(Me.Man, StateOfMan) End Sub Private Sub Wolf_Click() WolfInBoat 'Call IntoBoat(Me.Wolf, StateOfWolf) End Sub Private Sub Goat_Click() GoatInBoat 'Call IntoBoat(Me.Goat, StateOfGoat) End Sub Private Sub Cabbage_Click() CabbageInBoat 'Call IntoBoat(Me.Cabbage, StateOfCabbage) End Sub
Обработчики событий, как я и говорил ранее, очень простые, они вызывают соответствующую процедуру обработки события из стандартного модуля. Я реализовал две стратегии обработки, поэтому в теле обработчика предусмотрен вызов двух различных процедур. Один из этих вызовов закомментирован. Вот тексты процедур, соответствующие действующим (не закомментированным) вызовам:
Public Sub ManInBoat() 'Посадка пассажиров в лодку If StateOfMan = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfMan = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Man.Top = .Boat.Top - 30 .Man.Left = .Boat.Left + 25 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub Public Sub WolfInBoat() 'Посадка пассажиров в лодку If StateOfWolf = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfWolf = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Wolf.Top = .Boat.Top - 5 .Wolf.Left = .Boat.Left + 50 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub Public Sub GoatInBoat() 'Посадка пассажиров в лодку If StateOfGoat = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfGoat = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Goat.Top = .Boat.Top - 20 .Goat.Left = .Boat.Left + 100 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub Public Sub CabbageInBoat() 'Посадка пассажиров в лодку If StateOfCabbage = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfCabbage = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Cabbage.Top = .Boat.Top + 5 .Cabbage.Left = .Boat.Left + 5 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub
Я написал четыре почти одинаковые процедуры для размещения каждого из героев в лодке. Каждому из них отведено постоянное место в лодке и координаты подобраны так, чтобы изображение казалось бы более правдоподобным. Однако с программистской точки зрения это решение может показаться не самым удачным, в особенности при большом числе объектов. Поэтому я приведу и второй вариант, когда размещение любого из объектов выполняется одной и той же процедурой, имеющей в этом случае два параметра - видимый образ объекта и его состояние:
Public Sub IntoBoat(Im As Image, ByRef St As String) 'Посадка пассажиров в лодку If St = StateOfBoat Then 'лодка и пассажир на одном берегу St = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку Im.Left = .Boat.Left + 5 + CountInBoat * 50 Im.Top = .Boat.Top - CountInBoat * 30 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub
Заметьте, теперь координаты, которые получает размещаемый объект, зависят не только от расположения лодки, но и от числа пассажиров в ней. Обращение к этой процедуре в обработчике событий закомментировано. При желании можно пользоваться любым из двух приведенных вариантов.
Для ребенка, играющего в эту игру, вряд ли на первых порах будет понятно, почему щелчок по объекту приводит к его перемещению в лодку. Интуитивно, более разумным действием, приводящим к посадке объекта в лодку, было бы прямое перетаскивание объекта в лодку из его текущего положения на берегу. Реализацией такого способа перемещения объекта мы сейчас и займемся. При этом мы не будем тащить сам объект, хотя и это не трудно было бы сделать, а используем технику, основанную на специальном объекте DataObject, входящем в состав библиотеки MSForms. Эта техника хороша тем, что она позволяет опустить перетаскиваемый объект точно в лодку. Всякая попытка опустить объект где-либо в другом месте приведет к неуспеху операции перетаскивания. Момент, когда цель перетаскивания достигнута, и можно отпустить нажатую до этого кнопку мыши, определяется тем, что изменяется внешний вид курсора (появляется значок "+"), что и позволяет точно опустить объект в нужное место. Реализацию этого довольно сложного сп
особа перетаскивания обеспечивают обработчики трех событий. Первое из этих событий связано с самим перетаскиваемым объектом. В тот момент, когда над объектом нажимается левая кнопка мыши с целью начать его перетаскивать или копировать в точку назначения, возникает событие "MouseMove". В обработчике этого события и следует создать новый объект класса DataObject, определить некоторые его свойства и вызвать метод StartDrag. Этот метод работает совсем не так, как большинство обычных методов. Его действие оканчивается в тот момент, когда завершится операция перетаскивания. В качестве результата метод возвращает 0, если операция закончилась неуспехом, и ненулевое значение в противном случае. Заметьте, что во время перетаскивания, то есть еще до того, как StartDrag завершит работу, будут возникать другие события и, следовательно, будут работать другие обработчики событий. Два таких события будут возникать, когда перетаскиваемый объект достигает точки назначения, точнее, области назначения. В этот момент у целевого
объекта возникает событие BeforeDragOver. Обработчик этого события изменяет внешний вид курсора, что является сигналом достижения цели назначения и позволяет отпустить нажатую кнопку мыши. Обработчик второго события у целевого объекта BeforeDropOrPaste и реализует операцию опускания объекта. Если все завершится благополучно, то успехом заканчивает свою работу и метод StartDrag, которому возвращается управление. Подробнее обо всех деталях работы с этим объектом можно прочитать в моей книге, ссылка на соответствующее место в которой была уже сделана.
После всех этих пояснений можно привести и обработчики соответствующих событий, реализующих операции по перетаскиванию объектов в лодку:
Private Sub Man_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Man" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes8 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub Private Sub Wolf_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Wolf" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes9 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub Private Sub Goat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Goat" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes10 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub Private Sub Cabbage_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Cabbage" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes11 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub
Заметьте, для четырех перемещаемых объектов: человека, волка, козы и капусты написаны четыре обработчика события MouseMove. Каждый из них создает свой объект DataObject, запоминает в его свойстве Text название перемещаемого объекта и запускает метод StartDrag. Но цель у всех этих объектов одна - лодка. У объекта Boat два обработчика событий, вне зависимости от числа пассажиров лодки. Обработчик события BeforeDragOver для всех перемещаемых объектов один и тот же, поскольку его задача изменить вид курсора при попадании перемещаемого объекта в область назначения. Обработчик события BeforeDropOrPaste более сложный. Он должен произвести разбор случаев и определить, какой именно объект прибыл в точку назначения и соответствующим образом расположить его в лодке. Анализ свойства Text, объекта DataObject, переданного в качестве параметра обработчику событий, позволяет провести разбор случаев. После этих предварительных замечаний приведем тексты самих обработчиков:
Private Sub Boat_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора. Cancel = True Effect = fmDropEffectCopy End Sub Private Sub Boat_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения. Cancel = True Effect = fmDropEffectCopy 'Разбор случае. Анализ прибывшего объекта и вызов процедуры его размещения в лодке If Data.GetText = "Man" Then ManInBoat ElseIf Data.GetText = "Wolf" Then WolfInBoat ElseIf Data.GetText = "Goat" Then GoatInBoat Else: CabbageInBoat End If End Sub
На этом закончим рассмотрение задачи посадки пассажиров в лодку и перейдем к рассмотрению следующей задачи.
По ходу игры лодка с пассажирами должна переезжать с одного берега на другой. И опять-таки, я реализовал два варианта интерфейса. В первом, более простом варианте щелчок по лодке, заставляет ее переправиться на другой берег, если соблюдены все условия игры, в лодке есть человек и никто никого не кушает. В обработчике события Click объекта Boat вызывается соответствующая процедура, которая требуемым образом изменяет координаты, как самой лодки, так и находящихся в ней пассажиров. Вот текст обработчика и вызываемой в нем процедуры Crossing:
Private Sub Boat_Click() 'Переправа на другой берег Crossing End Sub Public Sub Crossing() 'Переправа на другой берег 'Есть ли человек в лодке With WGCForm If StateOfMan = "InBoat" Then If StateOfBoat = "LeftBank" Then 'Едем на правый берег 'Меняем координаты лодки и кормчего StateOfBoat = "RightBank" .Boat.Left = .Boat.Left + WidthOfRiver .Man.Left = .Man.Left + WidthOfRiver 'Анализ присутствующих пассажиров If StateOfWolf = "InBoat" Then .Wolf.Left = .Wolf.Left + WidthOfRiver End If If StateOfGoat = "InBoat" Then .Goat.Left = .Goat.Left + WidthOfRiver End If If StateOfCabbage = "InBoat" Then .Cabbage.Left = .Cabbage.Left + WidthOfRiver End If Else 'Едем на левый берег 'Меняем координаты лодки и кормчего StateOfBoat = "LeftBank" .Boat.Left = .Boat.Left - WidthOfRiver .Man.Left = .Man.Left - WidthOfRiver 'Анализ присутствующих пассажиров If StateOfWolf = "InBoat" Then .Wolf.Left = .Wolf.Left - WidthOfRiver End If If StateOfGoat = "InBoat" Then .Goat.Left = .Goat.Left - WidthOfRiver End If If StateOfCabbage = "InBoat" Then .Cabbage.Left = .Cabbage.Left - WidthOfRiver End If End If End If End With End Sub
Более интересна техника перетаскивания лодки с берега на берега. Ранее лодка служила целью назначения перетаскиваемых в нее объектов. Теперь она сама станет перемещаемым объектом и, следовательно, для нее мы создадим обработчик события MouseMove, в котором и вызовем метод StartDrag объекта DataObject. В роли целевых будут выступать в зависимости от ситуации объекты LeftBank и RightBank, для каждого из которых будут написаны по два обработчика событий BeforeDragOver и BeforeDropOrPaste. О них позже пойдет более подробный разговор, а сейчас для лодки приведем текст обработчика события MouseMove, запускающего процесс перемещения:
Private Sub Boat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Boat" 'Запоминаем имя перемещаемого объекта Effect = MyDataObject.StartDrag 'запускаем процесс перемещения If Effect = 0 Then перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes15 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub
Как видите, ничего нового в этом обработчике нет в сравнении с уже рассмотренными подобными обработчиками, все они построены по одной схеме. Больший интерес представляет обработка событий для целевых объектов, представляющих левый или правый берег, куда причаливает лодка. Но эти объекты являются целевыми и при высадке пассажиров на берег, когда они высаживаются (перетаскиванием) на берег. Поэтому отложим их описание до следующего параграфа, где будет рассмотрена высадка пассажиров из лодки.
Эта операция представляет процесс, обратный процессу посадки в лодку. Опять-таки, я реализовал две возможных стратегии. После того, как лодка причалила к берегу, одним щелчком по берегу можно высадить всех пассажиров из лодки. Вторая стратегия соответствует интуитивному поведению, когда пассажиров, не обязательно всех, можно высаживать по одному путем простого их перетаскивания на берег. Рассмотрим вначале более простую стратегию, реализованную в обработчиках событий Click для двух объектов LeftBank и RightBank:
Private Sub LeftBank_Click()
'Все пассажиры из лодки высаживаются на левый берег
ONLeftBank ("All")
End Sub
Private Sub RightBank_Click()
'Все пассажиры из лодки высаживаются на правый берег
OnRightBank ("All")
End Sub
Сами обработчики являются простыми, поскольку вся обработка спрятана в вызываемых процедурах стандартного модуля, которым в качестве параметра передается слово "All", указывающее на необходимость высадки на берег всех пассажиров. Эта же процедура будет использоваться и во второй стратегии, когда пассажиры высаживаются по одному. Давайте перейдем к ее рассмотрению. Для самих перетаскиваемых объектов обработчики события MouseMove, запускающие перетаскивание объекта, уже написаны. Мы рассматривали их, когда речь шла о перемещении объектов с берега в лодку. Теперь изменилась цель перетаскивания из лодки на берег, левый или правый в зависимости от того, куда причалила лодка. Поэтому нам надо лишь рассмотреть обработчики событий для целевых объектов LeftBank и RightBank. Напомним, эти объекты являются целевыми и при причаливании лодки к берегу. Вот тексты этих обработчиков:
Private Sub LeftBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора. Cancel = True Effect = fmDropEffectCopy End Sub Private Sub LeftBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения. Cancel = True Effect = fmDropEffectCopy 'Пассажир высаживается на левый берег или лодка причаливает к нему ONLeftBank (Data.GetText) End Sub Private Sub RightBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора. Cancel = True Effect = fmDropEffectCopy End Sub Private Sub RightBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения. Cancel = True Effect = fmDropEffectCopy 'Пассажир высаживается на правый берег или лодка причаливает к нему OnRightBank (Data.GetText) End Sub
Все программистские сложности спрятаны в процедурах стандартного модуля OnLeftBank и OnRightBank. Именно в них предусмотрен разбор случаев, то ли лодка причалила к берегу, то ли человек, то ли какой-либо из его спутников высаживается на берег. Здесь же предусмотрена проверка того, на какой берег в данный момент возможна высадка или причаливание.
Public Sub ONLeftBank(Who As String) If StateOfBoat = "LeftBank" Then 'пассажиров из лодки можно высадить на левый берег With WGCForm If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then 'можно высадить человека .Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10 StateOfMan = "LeftBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then 'можно высадить волка .Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10 StateOfWolf = "LeftBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then 'можно высадить козу .Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10 StateOfGoat = "LeftBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then 'можно высадить капусту .Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10 StateOfCabbage = "LeftBank" CountInBoat = CountInBoat - 1 End If End With ElseIf (Who = "Boat") Then 'Лодка переезжает на левый берег Crossing End If TestingState End Sub Public Sub OnRightBank(Who As String) If StateOfBoat = "RightBank" Then 'пассажиров из лодки можно высадить на правый берег With WGCForm If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then 'можно высадить человека .Man.Top = .RightBank.Top + 15: .Man.Left = .RightBank.Left + 10 StateOfMan = "RightBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then 'можно высадить волка .Wolf.Top = .RightBank.Top + 80: .Wolf.Left = .RightBank.Left + 10 StateOfWolf = "RightBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then 'можно высадить козу .Goat.Top = .RightBank.Top + 140: .Goat.Left = .RightBank.Left + 10 StateOfGoat = "RightBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then 'можно высадить капусту .Cabbage.Top = .RightBank.Top + 200: .Cabbage.Left = .RightBank.Left + 10 StateOfCabbage = "RightBank" CountInBoat = CountInBoat - 1 End If End With ElseIf (Who = "Boat") Then 'Лодка переезжает на правый берег Crossing End If TestingState End Sub
Нам осталось рассмотреть еще пару важных моментов, завершающих реализацию игры. Всякий раз, когда объекты игры изменяют свое состояние, необходимо проверять, является ли оно допустимым, не может ли съесть волк козу, или коза капусту. Многократно вызываемая по ходу дела процедура TestingState осуществляет все эти проверки:
Public Sub TestingState() 'Тестирование состояний With WGCForm 'Волк съел козу If (StateOfWolf = StateOfGoat) And (StateOfWolf <> StateOfMan) Then .Goat.Visible = False MsgBox Prompt:=Mes1 + vbCrLf + Mes3, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 InitialStates End If 'Коза съела капусту If (StateOfCabbage = StateOfGoat) And (StateOfCabbage <> StateOfMan) Then .Cabbage.Visible = False MsgBox Prompt:=Mes1 + vbCrLf + Mes4, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 InitialStates End If 'Слишком много пассажиров! If (CountInBoat > 2) Then If StateOfMan = "InBoat" Then .Man.Visible = False If StateOfWolf = "InBoat" Then .Wolf.Visible = False If StateOfGoat = "InBoat" Then .Goat.Visible = False If StateOfCabbage = "InBoat" Then .Cabbage.Visible = False MsgBox Prompt:=Mes1 + vbCrLf + Mes5, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 InitialStates End If 'Конец игры - Все пассажиры собрались на правом берегу If (StateOfMan = "RightBank") And (StateOfWolf = "RightBank") And _ (StateOfGoat = "RightBank") And (StateOfCabbage = "RightBank") Then MsgBox Prompt:=Mes2 + vbCrLf + Mes6, Buttons:=vbExclamation + vbOKOnly, Title:=Mes14 End If End With End Sub
При обнаружении критической ситуации, например, когда волк, оставленный без присмотра, съедает козу, образ козы исчезает с игрового поля и выдается соответствующее сообщение о том, что произошло ужасное событие и игра не может быть продолжена. Взгляните, как выглядит эта ситуация:
(рис 6.2) Одна из критических ситуаций в игре "Волк, Коза и Капуста"Заметим, что после нажатия кнопки "OK" в окне выдачи сообщения вызывается процедура InitialStates и игра возвращается в начальное состояние. Скажем еще несколько слов об организации диалога. Все константы, используемые в диалоге, собраны в разделе объявлений стандартного модуля WGCModule:
'Константы для организации диалогов Public Const Mes1 = "Ужасно, нелепо и грустно! " Public Const Mes2 = "Прекрасно, как славно, отлично! " Public Const Mes3 = " Волк съел козу! " Public Const Mes4 = " Коза съела капусту! " Public Const Mes5 = " Лодка перевернулась, и все утонули! " Public Const Mes6 = " Вам это удалось! Все переправились!" Public Const Mes7 = " Не удалось дотащить " Public Const Mes8 = " Человека! " Public Const Mes9 = " Волка! " Public Const Mes10 = " Козу! " Public Const Mes11 = " Капусту! " Public Const Mes12 = " Повторите попытку! " Public Const Mes13 = " Беда!" Public Const Mes14 = " Радость! " Public Const Mes15 = " Лодку! "
Такой подход позволяет при необходимости легко перейти на другой язык диалога, или сделать выбор языка параметром игры. Разумно также, но я не стал этого делать, собрать все диалоги в специальной процедуре Dialogs. Я отказался также от использования объекта при ведении диалога, хотя он очень подходит для подобных игр. Последнее решение обусловлено тем, что мне хотелось в чистом виде сохранить концепцию документа - обложки, а объект является специфическим объектом Office 2000. Его использование не позволило бы перенести игру без всяких хлопот в чистый VB.
Игра заканчивается, когда играющему удается перевезти всех путников на правый берег, и по этому случаю выдается радостное сообщение. Вот как оно выглядит:
(рис 6.3) Окончание игры "Волк, Коза и Капуста"Чтобы начать игру с начала, достаточно щелкнуть по объекту Shark, который до сих пор никак еще не использовался. В ответ на событие Click вызывается процедура InitialStates:
Private Sub Shark_Click() InitialStates End Sub
Для того чтобы игра начиналась с открытием документа - обложки, зададим обработку события Open для этого документа:
Private Sub Document_Open() WGCForm.Show End Sub
На этом я завершаю описание реализации этой игры. Конечно, моя цель состояла не в том, чтобы создать совершенную реализацию данной игры. Реализация игры, скорее побочный продукт моей работы. Я выбрал эту простую игру в качестве хорошего примера, на котором я постарался показать, как создаются документы - обложки, как можно создать и использовать визуальные объекты. Что же касается самой игры, то ее несомненно можно усовершенствовать. Вот несколько возможных направлений работы для тех, кто хотел бы довести эту игру до "товарного" вида:
Рассмотрим еще один вопрос, не связанный напрямую с реализацией игры, но относящиеся к нашей теме.
Я уже говорил, что одно из достоинств документов типа "обложка", что они сравнительно легко переносятся в другие программные среды. Проще всего, программный проект, сделанный на VBA и находящийся под обложкой одного из документов Office 2000, перенести в чистый VB. Для этого не потребуется почти никаких усилий. Этим мы сейчас и займемся. Наш программный проект состоит из одной формы WGCForm и одного стандартного модуля WGCModule. Нет никаких проблем, чтобы экспортировать эту форму и модуль, сохранив их в виде файлов с уточнениями frm и соответственно. Для этого достаточно выбрать пункт ExportFile из главного меню File или из контекстного меню, доступного при нажатии правой кнопки на модуле или форме в окне проекта. После того, как форма и модуль сохранены, они могут быть импортированы в любой из документов Office 2000, где начнут работать под другой обложкой. Более интересно, что точно также они могут быть импортированы в чистый VB, что я и сделал. Взгляните, как выглядит проект VB после переноса в
него нашей формы и модуля.
(рис 6.4) Проект WGC, перенесенный в среду VB6 Следует обратить внимание на то, что формы VB и VBA это все-таки разные формы для VB. Формы VBA загружаются в среде VB специальным загрузчиком таких форм ActiveX Designer MSForms. Этот дизайнер включен во все версии VB6, поэтому на компьютере достаточно иметь VB и нет необходимости в существовании Office 2000. Подобно всем другим дизайнерам VB он имеет собственную динамически подключаемую библиотеку (run time dll). Это приводит к увеличению накладных расходов, но сам перенос осуществляется без проблем. Теперь, имея проект на VB, можно использовать все преимущества этой среды, сохранив наш проект, например, в виде исполняемого exe - файла, в виде DLL или в виде ActiveX Control.
Следует сделать одно важное замечание, связанное с переносом в VB. Мне все-таки пришлось внести одно изменение в программный текст, прежде, чем проект заработал. Это изменение связано с объектом DataObject. Дело в том, что в VBA - проекте я использовал этот объект, не уточнив его полное имя. По умолчанию предполагалось, что речь идет об объекте DataObject библиотеки MSForms. Когда же проект стал компилироваться в чистом VB, то по умолчанию этот объект стал восприниматься, как объект VB и здесь возникла некоторая путаница. Хотя язык VB позволяет работать с визуальными объектами библиотеки MSForms, он имеет и собственную, весьма похожую библиотеку визуальных объектов. Замечу, что объект DataObject существует в этой библиотеке и, выполняя практически те же функции, является более мощным объектом. Вместо методов GetText и SetText он обладает методами GetData и SetData, способными передавать не только текст, но и данные более сложной структуры, в том числе метафайлы и графические файлы с уточнением bmp. Конеч
но, я не стал переписывать текст, работая с новым, более мощным объектом VB. Я всюду в тексте заменил DataObject на полное имя MSForms.DataObject. После чего все заработало, как нужно.
Несколько слов об обратном переносе программных проектов из VB в VBA. Такой перенос возможен, хотя выполняется сложнее. Прежде всего, замечу, что формы VB не переносятся напрямую в VBA. Для того, чтобы из VB перенести форму, необходимо разрабатывать ее, используя ActiveX Designer MSForms. Ну и, конечно, нужно следить за тем, чтобы реализация не использовала такие объекты VB, для которых нет точных аналогов в VBA, например, не следует в реализации использовать возможности объекта DataObject, принадлежащего чистому VB.
Я уже говорил о том, что офисному программисту приходится начинать свою работу над документом не с нуля. Зачастую, документ в значительной своей части создан вручную и обладает широким спектром стандартных возможностей. Задача программиста состоит в том, чтобы придать документу новые возможности, учитывающие специфику решаемых задач. Документы, созданные вручную и не требующие программирования или требующие его лишь в малой степени, я и называю документами типа "ручная работа". Хотя с позиций офисного программирования такие документы представляют крайний, предельный случай и менее интересны, чем "настоящие", "сшитые на заказ" документы, но нужно отчетливо понимать, что документы " ручной работы" широко распространены. Чаще всего такие документы создают сами пользователи, но иногда эту работу необходимо выполнять и программисту. Созданию и работе с такими документами посвящено большое количество книг и статей, адресованных пользователям. Это отдельная и большая тема, выходящая за пределы рассматриваемых мной вопросов, но полностью обойтись без их рассмотрения не удается, поскольку это естественная часть работы офисного программиста. Поэтому я хочу рассмотреть один пример создания документа подобного рода, а главное, поговорить о том, что для таких документов иногда возможно создание программного проекта без явного программирования, используя возможности такого средства как упоминавшийся мной MacroRecorder.
Разработка электронной формы бланка заказа.
В офисе работают с людьми и с документами. Оставим в стороне людей и займемся документами. В современном офисе документы имеют электронную форму. Давайте рассмотрим все этапы создания документа в электронной форме на примере бланка "СЧЕТ-ФАКТУРА" - типичного документа многих офисов. Будем создавать этот документ вручную, не прибегая к программированию, используя только стандартные возможности Office 2000. Конечно же, используя программирование на VBA, этот документ можно сделать куда более содержательным, например, связав его с базой данных. Но отложим рассмотрение этих вопросов на будущее и сосредоточимся на работе вручную и автоматическому получению макросов, запуск которых будет повторять работу, которую мы будем делать руками.
Прежде, чем браться за дело, дадим несколько общих советов, которыми следует руководствоваться при разработке электронных бланков:
Следуя этим советам, я создал эскиз бланка "СЧЕТ-ФАКТУРА". Конечно, для таких бланков есть зафиксированный стандарт. Я не стал строго следовать этому стандарту, поскольку преследую другие цели. Тем не менее, возможно, что бланк, который будет построен, может послужить шаблоном для создания "настоящего" бланка "СЧЕТ-ФАКТУРА". Наш бланк будет содержать:
(рис 6.5) Эскиз электронного бланкаКонечно, внимательный читатель понял, что перед ним не "бумажный" рисунок, а реальный документ, разработанный средствами Office. Но в каком из приложений он создан? Мог ли он догадаться, что перед ним рабочая страница Excel? Аналогичный бланк можно было бы создать и в других приложениях Office таких, как Word и Access. Сейчас я подробно опишу, как был создан в Excel подобный шаблон, отступая, возможно, в некоторых деталях, так что окончательный результат будет немного отличаться от показанного на рисунке.
Разработка шапки электронного бланка
Как уже говорилось, шапка бланка будет включать логотип офиса, его название и другие реквизиты. Так как шапка может применяться при создании разных бланков, стоит разработать для нее отдельный шаблон. Более того, используя Macrorecorder, будет создан макрос, строящий эту шапку. Сразу же замечу, что это будет не один макрос, а серия небольших макросов, отражающих отдельные этапы работы. Я полагаю, что это правильная технология использования MacroRecorder. Макросы должны быть небольшими и соответствовать отдельным функциям, которые можно выделить естественным образом. В этом случае набор таких макросов допускает переиспользование и процесс создания нового шаблона во многом может свестись к сборке из набора готовых макросов. Мы пойдем по этой дороге, и созданные макросы помогут нам проследить за каждым шагом работы вручную, даже если мы и не опишем подробно его на словах.
Выделим следующие этапы в создании шапки:
Обычный лист Excel содержит "лишние" для бланка детали. Пользователь, работающий с бланком, может и не подозревать, что он работает с рабочим листом Excel, он должен видеть перед собой "обычный" лист с именованными полями, в которые заносится нужная информация. Поэтому я отключил такие элементы рабочего листа, как сетку, выделяющую ячейки, заголовки столбцов и строк, а также строку листа Excel, в которой отображаются имена ячеек и формулы, записанные в них. Это достигается установкой соответствующих переключателей в окне Options (Параметры) из меню Tools (Сервис). Следующим шагом работы является выделение области ячеек, соответствующих размеру электронного бланка и выделение ее подходящим цветом фона. На этом этапе я использовал возможности, предоставляемые для задания фона в окне Patterns (Вид) при выборе команды Cells (Ячейки) из меню Format (Формат). Замечу, что хотя совершенно естественно начинать описание процесса создания бланка с его форматирования, реально форматирование следует выполнять на п оследнем этапе, когда бланк полностью готов и осталось убрать лишние детали.
Итак, вот моя программа действий по форматированию бланка с записью соответствующего макроса:
Приведу текст полученного макроса с добавленными мной комментариями:
Sub ФорматированиеБланка()
'
' ФорматированиеБланка Macro
' Macro recorded 27.11.1999 by Vladimir Billig
' Этот макрос удаляет сетку, заголовки строк и столбцов, строку формул,
'показ нулей в ячейках рабочего листа. Выделяет подходящим фоном рабочую
'область бланка. В данном примере: A1:K56
Range("A1:K56").Select
With Selection.Interior
.ColorIndex = 15 'серый цвет
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
End With
With ActiveWindow
.DisplayGridlines = False 'сетка
.DisplayHeadings = False 'заголовки
.DisplayFormulas = False 'показ формул
.DisplayZeros = False 'отображение нулей
End With
Application.DisplayStatusBar = False 'строка статуса
End Sub
MacroRecorder помещает все создаваемые макросы в отдельный модуль, который я переименовал, назвав его "БланкСчетФактура".
Каждый приличный офис имеет логотип. Наш офис, который мы будем называть "Родная Речь" такой логотип имеет. Под логотипом мы понимаем графический образ, являющийся символом организации и размещаемый на ее бланках. Допустим, рисунок логотипа уже создан, и нам остается только поместить его в нужном месте бланка, возможно, согласовав размеры. Есть две возможности размещения его на бланке. При создании логотипа офиса "РР" я нашел рисунок бабочки и пририсовал в графическом редакторе на каждом из ее крылышек букву "Р", затем привел рисунок к нужным размерам. Чтобы поместить его на бланк, можно вставить рисунок в ячейку рабочего листа. Для этого нужно выделить подходящую ячейку и указать имя файла в окне, раскрывающемся при выборе команды From File (Из Файла) пункта Picture (Рисунок) меню Insert (Вставить). Но лучше идти более сложным путем: разместить на бланке элемент управления "Image", а затем, используя свойство Picture этого элемента, указать, где хранится рисунок. Несомненным преимуществом такого подхода является возможность использования рисунка произвольного размера. Меняя его свойства, можно его растянуть или сжать до приемлемых размеров. Кроме того, этот элемент управления реагирует на события и можно написать процедуру, которая, например, будет выдавать дополнительную информацию, скажем, о "главных" людях офиса, в тот момент, когда пользователь щелкнет кнопкой по области, занятой логотипом. Обращаем Ваше внимание на то, что при помещении на рабочий лист Excel элемента управления, он вставляется как OLE-объект и становится частью коллекции OLEObjects.
Я выбрал путь, использующий OLE-объекты. Мои действия записывал макрос, который был назван "Логотип":
Sub Логотип() ' ' Логотип Macro ' Macro recorded 27.11.1999 by Vladimir Billig 'Этот макрос создает элемент управления Image 'и помещает его в нужную позицию рабочего листа. ActiveSheet.OLEObjects.Add(ClassType:="Forms.Image.1", Link:=False, _ DisplayAsIcon:=False, Left:=23.25, Top:=15, Width:=63, Height:=61.5). _ Select End Sub
Заметьте, MacroRecorder сегодня не умеет следить за действиями, выполняемыми над самим элементом управления, поэтому мои действия по работе с элементом Image не нашли отражения в макросе. Так что каждый раз после запуска макроса работу по заданию свойств элемента управления необходимо повторять заново. Перечислю те изменения, которые я задавал в окне свойств элемента управления
Рядом с логотипом поместим название офиса: "Издательство Родная Речь". Чтобы эта надпись выглядела незаурядно, воспользуемся возможностями, которые предоставляет Shape. Я визуально менял свойства этих объектов, а Macrorecorder фиксировал мои действия в макросе с именем "КрасивоеИмя". Вот текст этого макроса:
Sub КрасивоеИмя() ' ' КрасивоеИмя Macro ' Macro recorded 27.11.1999 by Vladimir Billig 'Этот макрос включает инструментальную панель WordArt 'и с ее помощью создает два рисованных объекта класса Shape 'первый из них задает слово "Издательство", второй -"Родная Речь" ActiveSheet.Shapes.AddTextEffect(msoTextEffect10, "Издательство", _ "Arial Black", 24#, msoFalse, msoFalse, 177.75, 138.75).Select Selection.ShapeRange.IncrementLeft -34.5 Selection.ShapeRange.IncrementTop -126# ActiveSheet.Shapes.AddTextEffect(msoTextEffect4, _ "Р о д н а я Р е ч ь ", _ "Impact", 20#, msoFalse, msoFalse, 197.25, 138.75).Select Selection.ShapeRange.IncrementLeft -30# Selection.ShapeRange.IncrementTop -80.25 End Sub
Заключительный шаг в формировании шапки - заполнение полей с реквизитами офиса: адресом, телефоном и др. Визуально эту операцию выполнить просто. В одной ячейке записывается название реквизита, а в ячейке справа его значение. Обычно одной ячейки для значения не хватает, и требуется расширить это поле до нужных размеров. Чтобы не менять размеры столбцов, проще всего слить идущие подряд ячейки. Вручную для этого нужно выделить ячейки и включить переключатель MergeCells (Объединение ячеек) на вкладке Alignment (Выравнивание) из пункта Cells (Ячейки) меню Format (Формат).
Чтобы объединить поля с реквизитами и придать им некоторую структуру, их, как правило, заключают в рамку. Подходящую рамку можно выбрать из набора готовых Shape объектов, доступ к которым можно получить, включив инструментальную панель Рисование (Drawing) с элементами рисования; одна из ее кнопок - Автофигуры (AutoShapes) - позволяет добраться до базисных Shape-фигур. Когда рамка накладывается на поля с реквизитами, поля перестают быть видимыми. Рамку следует сделать прозрачной. Для этого нужно, выделив ее, щелкнуть правой кнопкой мыши, из контекстного меню выбрать пункт FormatAuto Shape и в появившемся окне в разделе заполнения (Fill) выбрать из списка значение для цвета NoFill (без заполнения). В этот момент при желании можно изменить стиль начертания границ рамки.
Я проделал все описанные выше операции и получил законченную шапку, отвечающую нашему эскизу. Макрос "РеквизитыИРамка" позволит проследить за моими действиями последнего этапа - формирования реквизитов офиса. Заметьте, в завершение шапки я подвел черту, подчеркнув сформированную часть яркой линией.
Sub РеквизитыИРамка()
'
' РеквизитыИРамка Macro
' Macro recorded 27.11.1999 by Vladimir Billig
'Именование полей реквизитов
Range("D9:E9").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Адрес"
Range("D10:E10").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Телефон, Факс"
Range("D11:E11").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Email"
Range("D12:E12").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "ИНН"
'Запись реквизитов
Range("F9:K9").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Москва, ул. Филевская, 15"
Range("F10:J10").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "175-3434, 175-3480"
Range("F11:J11").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "rr@red.ru"
Range("F12:J12").Select
Selection.MergeCells = True
Selection.HorizontalAlignment = xlLeft
ActiveCell.FormulaR1C1 = "198712345"
Range("F9:J12").Select
Selection.Font.Italic = True
'Построение рамки
ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
105#, 96#, 369.75, 63.75).Select
Selection.ShapeRange.Fill.Visible = msoFalse
'Линия отчеркивания
ActiveSheet.Shapes.AddLine(48.75, 189#, 475.5, 189#).Select
Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
Selection.ShapeRange.Line.Visible = msoTrue
Selection.ShapeRange.Line.Style = msoLineThinThin
Selection.ShapeRange.Line.Weight = 3#
End Sub
Макрос получился довольно большой, поскольку он оперирует с большим числом объектов, и для каждого из них перечисляются все свойства, в том числе и устанавливаемые по умолчанию. Я сократил его текст и добавил комментарии. Следует заметить, что это типичная ситуация, когда полученный макрос является основой для программиста, который оптимизирует его текст, редактирует его, вносит добавления и изменения, добиваясь нужного визуального эффекта.
Мы выполнили всю работу по созданию шапки, разделив ее на четыре этапа. На каждом из этих этапов Macrorecorder создал соответствующий макрос. Осталось объединить эти макросы в один, - мы назвали его "Шапка". Он очень прост: последовательно вызывает уже созданные макросы каждого шага:
Sub Шапка() 'Объединение четырех макросов для построения шапки бланка ФорматированиеБланка Логотип КрасивоеИмя РеквизитыИРамка End Sub
Запустив этот макрос на чистом листе, мы получим заполненную шапку. Точнее, почти заполненную, поскольку макрос "Логотип" не записал действий с объектом класса Image по изменению его свойств. Так что нужно приложить некоторые усилия и повторить действия по установлению свойств объекта, чтобы увидеть картинку, связанную с объектом Image. Взгляните на результат работы макроса "Шапка":
(рис 6.6) Построение шапки
Согласно эскизу следующая часть шаблона бланка заказа содержит информацию о покупателе, - реквизиты организации, заказывающей товар. Мы уже имели дело с реквизитами офиса "РР" при построении шапки. При построении реквизитов покупателя можно было бы в качестве образца воспользоваться макросом "РеквизитыИРамка", слегка подкорректировав его. В каком-то смысле данная задача даже проще, поскольку не нужно заполнять значения полей, задающих реквизиты. Эту работу делает менеджер в момент оформления заказа. Уточним программу действий:
Вот текст соответствующего макроса, записавшего мои действия:
Sub РеквизитыЗаказчика()
'
' РеквизитыЗаказчика Macro
' Macro recorded 28.11.1999 by Vladimir Billig
' Именование бланка
Range("C16:I16").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "СЧЕТ-ФАКТУРА № от "
With ActiveCell.Characters(Start:=1, Length:=31).Font
.FontStyle = "Полужирный"
.Size = 14
End With
With ActiveCell.Characters(Start:=32).Font
.FontStyle = "Полужирный Курсив"
.Size = 11
End With
'Задание полей реквизитов заказчика
Range("B19:C19").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Покупатель"
Range("B20:C20").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Адрес"
Range("B21:C21").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Тел., Факс, Email"
Range("B22:C22").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "ИНН"
Range("D19:J19").Select
Selection.MergeCells = True
Range("D20:J20").Select
Selection.MergeCells = True
Range("D21:J21").Select
Selection.MergeCells = True
Range("D22:J22").Select
Selection.MergeCells = True
'Создание рамки
ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
34.5, 231.75, 452.25, 63.75).Select
Selection.ShapeRange.Fill.Visible = msoFalse
'Надпись на рамке
ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, _
95, 225, 60, 15).Select
Selection.Characters.Text = "Покупатель"
'Реквизиты грузоотправителя и грузополучателя
Range("B25:D25").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Грузоотправитель и его адрес"
Range("E25:J25").Select
Selection.MergeCells = True
Range("B26:D26").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Грузополучатель и его адрес"
Range("E26:J26").Select
Selection.MergeCells = True
Range("B25:J26").Select
Selection.Font.FontStyle = "Полужирный Курсив"
Selection.Font.Size = 9
'Отчеркивание
ActiveSheet.Shapes.AddLine(44.25, 357.75, 500.25, 357.75).Select
Selection.ShapeRange.Line.Style = msoLineThinThin
Selection.ShapeRange.Line.Weight = 3#
Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
Selection.ShapeRange.Line.Visible = msoTrue
End Sub
Запустив макрос "РеквизитыЗаказчика", на рабочем листе с уже созданной шапкой я получил следующую часть бланка:
(рис 6.7) Шапка и раздел с реквизитами заказчика
Основная часть этого бланка - таблица со сведениями о заказываемых товарах. Сетка, которая обычно очерчивает границы ячеек рабочего листа, была удалена, на электронном бланке заказа она неуместна. Теперь необходимо восстановить некоторые из этих границ, чтобы нарисовать таблицу в привычной для глаз форме. На этом этапе я буду работать с вкладкой Borders ("Границы"), открываемой в окне Format Cells ("Формат ячеек") из меню Format ("Формат"). С объектной точки зрения границы объекта класса Range составляют коллекцию Borders. Меняя свойства элементов этой коллекции (объектов Border), можно добиться нужного эффекта. Я построю таблицу в три этапа:
Такое разделение сделает обозримыми макросы, транслирующие мои действия в тексты на VBA.
Построение столбцов
Столбцы таблицы это ее поля. Размер поля, его ширина зависит от содержания поля. В Excel требуемого размера можно достичь двумя путями объединением (слиянием) ячеек, составляющих одно поле, или изменением размера соответствующего столбца Excel, отведенного для поля. Поскольку второй способ действует на всю таблицу и может привести к изменению внешнего вида уже сформатированного листа, то применять его следует с определенной осторожностью. При применении такого способа рекомендуется начинать форматирование документа с создания таблицы и соответствующего изменения размеров ее столбцов. В данном случае применяются оба способа, изменяются размеры нескольких столбцов и сливаются ячейки для поля, задающего название товара.
Для решения задачи я выделил первую строку таблицы, слиянием ячеек и передвижкой границы между столбцами добился нужных размеров полей таблицы, а затем, используя вкладку "Границы", выделил внешние и внутренние вертикальные границы. После чего осталось скопировать формат этой строки на нужное количество строк таблицы. Вот макрос, выполняющий эти действия:
Sub СтолбцыТаблицы()
'
' СтолбцыТаблицы Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'Изменение ширины полей, меняя размеры столбцов
Columns("E:E").ColumnWidth = 4
Columns("F:F").ColumnWidth = 4
Columns("I:I").ColumnWidth = 4.43
Columns("K:K").ColumnWidth = 11.86
'Слияние ячеек для поля Название Товара
Range("A32:D32").Select
Selection.MergeCells = True
'Выделение внешних границ: слева,снизу, справа
Range("A32:K32").Select
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.Weight = xlThin
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.Weight = xlThin
End With
'Выделение вертикальной внутренней границы
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin
End With
'Копирование формата на всю область таблицы
Selection.Copy
Range("A33:K46").Select
Selection.PasteSpecial Paste:=xlFormats, Operation:=xlNone, _
SkipBlanks:=False, Transpose:=False
Application.CutCopyMode = False
End Sub
Шапка таблицы
Шапка таблицы будет состоять из двух строк, в первой содержатся названия полей, во второй их индексы. Используя соответствующие атрибуты на вкладке Alignment (Выравнивание), я задал центрирование текста по вертикали и горизонтали, а также автоматическое изменение высоты строки, чтобы текст названия полей был полностью видимым. Кроме того, я выделил графически границы шапки, задав их двойными линиями. Макрос получается, конечно, большим, поскольку оперирует с большим числом объектов. Вот его текст:
Sub ШапкаТаблицы()
'
' ШапкаТаблицы Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
'Именование полей таблицы и задание индексов
Range("A32:D32").Select
ActiveCell.FormulaR1C1 = "Наименование товара"
Range("E32").Select
ActiveCell.FormulaR1C1 = "Единица измерения"
Range("F32").Select
ActiveCell.FormulaR1C1 = "Количество"
Range("G32").Select
ActiveCell.FormulaR1C1 = "Цена"
Range("H32").Select
ActiveCell.FormulaR1C1 = "Сумма"
Range("I32").Select
ActiveCell.FormulaR1C1 = "Ставка НДС"
Range("J32").Select
ActiveCell.FormulaR1C1 = "Сумма НДС"
Range("K32").Select
ActiveCell.FormulaR1C1 = "Всего с НДС"
Range("A33:D33").Select
ActiveCell.FormulaR1C1 = "1"
Range("E33").Select
ActiveCell.FormulaR1C1 = "2"
Range("F33").Select
ActiveCell.FormulaR1C1 = "3"
Range("G33").Select
ActiveCell.FormulaR1C1 = "4"
Range("H33").Select
ActiveCell.FormulaR1C1 = "5"
Range("I33").Select
ActiveCell.FormulaR1C1 = "6"
Range("J33").Select
ActiveCell.FormulaR1C1 = "7"
Range("K33").Select
ActiveCell.FormulaR1C1 = "8"
'Центрирование текста и изменение высоты строки,
'обеспечивающее видимость текста
Range("A32:K33").Select
With Selection
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = True
End With
'Выделение границ шапки
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin
End With
End Sub
Последняя строка
Последняя строка, как и строки шапки, отличается от остальных строк таблицы. Она используется для подведения итогов. Также как и шапку, я выделил ее границы графически. Итак, макрос "ПоследняяСтрока":
Sub ПоследняяСтрока()
'
' ПоследняяСтрока Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
'Задание итоговой строки
Range("A46:D46").Select
ActiveCell.FormulaR1C1 = "Всего к оплате"
Range("A46:K46").Select
With Selection.Font
.Name = "Arial"
.FontStyle = "Полужирный"
.Size = 11
End With
'Выделение границ итоговой строки
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin
End With
End Sub
Задание расчетных формул и форматирование полей
Одно из преимуществ построения таблиц в Excel состоит в том, что можно задать формулы для автоматического подсчета значений некоторых полей таблицы. В нашем случае все поля, задающие суммы, будут вычисляться по формулам. Формулы для расчета сумм достаточно очевидны и я не буду их выписывать. В тексте макроса они приведены. При работе вручную я в соответствующих столбцах для сумм задал эти формулы в первой рабочей строке таблицы, а затем скопировал их на оставшиеся рабочие строки. В строке итогов я задал суммирование по столбцам таблицы. Для столбцов таблицы я задал также подходящее форматирование данных. Вот текст макроса "Расчеты":
Sub Расчеты()
'
' Расчеты Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
'Форматирование полей и задание расчетных формул
'Форматирование поля "Название товара"
Range("A34:D45").Select
Selection.ShrinkToFit = True
'Форматирование поля "Единица Измерения"
Range("E34:E45").Select
Selection.HorizontalAlignment = xlCenter
'Форматирование поля "Цена"
Range("G34:G45").Select
Selection.NumberFormat = "0.00"
'Форматирование поля "Сумма"
Range("H34:H46").Select
Selection.NumberFormat = "0.00"
'Формула: Сумма = Цена * Количество
Range("H34").Select
ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
'Копирование формулы
Range("H34").Select
Selection.AutoFill Destination:=Range("H34:H45"), Type:=xlFillDefault
Range("H34:H45").Select
'Итоговая сумма
Range("H46").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
'Форматирование поля "НДС"
Range("I34:I45").Select
Selection.NumberFormat = "0%"
'Форматирование поля "Сумма НДС"
Range("J34:J46").Select
Selection.NumberFormat = "0.00"
'Формула: Сумма НДС = Сумма * НДС
Range("J34").Select
ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
'Копирование формулы
Range("J34").Select
Selection.AutoFill Destination:=Range("J34:J45"), Type:=xlFillDefault
Range("J34:J45").Select
'Итоговая сумма
Range("J46").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
'Форматирование поля "Всего с НДС"
Range("K34:K46").Select
Selection.NumberFormat = "0.00"
'Формула: Всего с НДС = Сумма + Сумма НДС
Range("K34").Select
ActiveCell.FormulaR1C1 = "=RC[-3]+RC[-1]"
'Копирование формулы
Range("K34").Select
Selection.AutoFill Destination:=Range("K34:K45"), Type:=xlFillDefault
Range("K34:K45").Select
'Итоговая сумма
Range("K46").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
Range("K47").Select
'Нулевые значения в таблице не отображаются
ActiveWindow.DisplayZeros = False
End Sub
Я собрал макросы, строящие отдельные части таблицы под одной обложкой в макросе "ТаблицаРасчеты":
Sub ТаблицаРасчеты() 'Построение таблицы СтолбцыТаблицы ШапкаТаблицы ПоследняяСтрока Расчеты End Sub
Запустив этот макрос на листе Excel, я получил таблицу, готовую для заполнения. Вот как она выглядит с заполненными двумя строчками. Заметьте, что все расчеты в ней ведутся автоматически.
(рис 6.8) Таблица продажи товаров бланка Счет-ФактураЗаключительный макрос "УтверждающиеПодписи"
Для полноты картины закончим создание бланка утверждающими подписями. Ни в действиях, ни в макросе, записывающем эти действия нет никаких особенностей, о которых стоило бы говорить. Это рутинная работа по помещению текста в ячейки Excel с подходящим для данного случая форматированием этого текста. Приведу текст этого макроса:
Sub УтверждающиеПодписи()
'
' УтверждающиеПодписи Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
Range("B50:G50").Select
Selection.MergeCells = True
With Selection.Font
.Name = "Arial"
.FontStyle = "Полужирный Курсив"
.Size = 11
End With
ActiveCell.FormulaR1C1 = "Ген. Директор _________________________"
Range("B52:G52").Select
Selection.MergeCells = True
With Selection.Font
.Name = "Arial"
.FontStyle = "Полужирный Курсив"
.Size = 11
End With
Range("B52:G52").Select
ActiveCell.FormulaR1C1 = "Гл. Бухгалтер __________________________"
Range("B55").Select
ActiveCell.FormulaR1C1 = "М. П."
End Sub
Сборка макросов. Макрос "СчетФактура"
Для завершения работы и получения макроса, который строит шаблон бланка "Счет-Фактура", осталось собрать все макросы, строящие отдельные части бланка. Вот текст заключительного макроса:
Sub СчетФактура()
'Этот заключительный макрос строит шаблон бланка Счет-Фактура
'Он вызывает макросы, строящие отдельный части этого бланка
Шапка
РеквизитыЗаказчика
ТаблицаРасчеты
УтверждающиеПодписи
End Sub
Запустив этот макрос на чистом листе Excel, я получил полностью сформированный бланк, который в дальнейшем можно использовать в качестве соответствующего шаблона.
Итак, приведен пример построения документа "ручной работы". Параллельно создан макрос (группа макросов), позволяющий почти полностью повторить эту работу программно. Давайте обсудим, что было сделано, подведем итоги и обсудим перспективы развития подобных документов:
При создании в Excel бланка "Счет-Фактура" MacroRecorder позволил получил макросы, записавшие практически все действия пользователя, создававшего этот бланк вручную. Единственное, что не позволил MacroRecorder, это проследить за действиями при работе с элементом управления, помещенным на бланк. Я уже говорил о том, что программисту полезно обращаться к MacroRecorder не только для того, чтобы получить соответствующий макрос, который он, возможно, использует в дальнейшем, чтобы не повторять действия вручную. Часто он обращается к MacroRecorder в процессе обучения работе с объектами Office 2000. В тех случаях, когда программист не знает с каким объектом ему лучше работать при решении той или иной задачи, какими свойствами и методами объекта следует пользоваться, то часто полезно обратиться к MacroRecorder, записать макрос и посмотреть, как MacroRecorder решает данную задачу. Например, задачей программиста является создание системы, позволяющей проводить оптимизацию инвестиций. Чтобы понять, как и с какими объектами Excel следует работать в данной ситуации, рекомендуется решить вручную несколько оптимизационных задач, записав при этом соответствующие макросы.
(рис 6.9) Построение таблицы в документе Word MacroRecorder в Office 2000 не позволяет записать действия по созданию таблиц в документе Word, хотя, конечно же, есть объект Table и возможно с его помощью программное создание таблиц.
Не стоит особенно печалиться по поводу сравнительно небольших возможностей MacroRecorder. Это лишь один из вспомогательных инструментов, используемых в офисном программировании. Главное, что есть в распоряжении программиста, работающего в среде Office 2000, - язык VBA, набор объектов из библиотек Office 2000, возможность использовать компоненты, созданные на других языках и в других программных средах, используя все эти средства можно решить практически все задачи, возникающие в жизни офиса при работе с его документами. На этом я и закончу введение в офисное программирование.
В чем же специфика офисного программирования, чем оно отличается от программирования в программной среде таких языков как VB,
Заметьте, что MacroRecorder помимо своей основной функции играет и важную обучающую роль. Во многих случаях, когда я затруднялся понять, с какими объектами мне следует работать для решения той или иной задачи, я включал MacroRecorder, решал задачу вручную, а затем анализировал текст полученного макроса, при необходимости занимался его оптимизацией, доводя его до нужной кондиции.
К сожалению, Microsoft все в меньшей степени поддерживает MacroRecorder в своих новых разработках.
Подведем теперь некоторые итоги и постараемся ответить на два главных вопроса:
Итак, давайте рассмотрим, что может дать офисное программирование пользователям Office. В свое время, обращаясь к пользователям и рассказывая им о преимуществах офисного программирования, я, стараясь сделать свою мысль о важности настройки документов Office образной, формулировал ее следующим образом:
"Работать с документами Office 2000, не используя VBA, все равно, что играть на ненастроенном пианино. Играть, конечно, можно, но профессионал этого позволить себе не может".
Используя это сравнение, хотелось обратить внимание пользователей на важность, а главное, на возможность настройки документов на решение специфических задач пользователя. Программирование офисных документов это одно из основных достоинств Office и грешно не использовать такую возможность. Документ, "сшитый по заказу" (custom
Однако, мое сравнение, как и всякое сравнение "хромает". Его можно интерпретировать и по-другому: "Фирма Microsoft предлагает своим пользователям ненастроенные документы ("расстроенное пианино"). Чтобы с ними работать, нужно их предварительно настраивать". Дабы избежать подобных заключений, несправедливых по отношению к этому прекрасному продукту, я теперь предпочитаю в подобных ситуациях говорить так:
"Работать с документами Office 2000 все равно, что играть на хорошо настроенном пианино. Но у этого инструмента есть возможности, открывающиеся для профессионалов. Нажмите ALT + F11 и создайте собственную композицию".
Вот только некоторые из тех преимуществ, что получает конечный пользователь, использующий программируемые офисные документы:
А есть ли преимущества у программиста, работающего в Office? Есть, и их, пожалуй, не меньше, чем у пользователя:
Заключая этот параграф, хочется еще раз напомнить пользователям и программистам, что именно их совместная работа над офисными документами может дать наилучшие результаты. В этом состоит одна из главных особенностей офисного программирования.
Давайте перейдем от лозунгов и призывов к конкретным делам. Для начала рассмотрим создание документа - обложки. Как мы говорили ранее, это некоторый специальный вид офисных документов, в которых не требуется знания объектов Office 2000. Такой документ представляет программный проект под обложкой одного из офисных документов, причем не важно какую из обложек выбрать - Word, Excel или, например, Power Point. Создается такой документ почти также как и программный проект в любой визуальной среде. По этой причине он достаточно легко переносится в любую из этих сред. Типичным примером таких документов, как мы говорили, являются игры. Рассмотрим посему реализацию хорошо известной игры "Волк, Коза и Капуста". Для понимания реализации нужно знать основы VBA и работу с интерфейсными объектами формами и их элементами.
Всем известна задача о "Волке, козе и капусте". Вот ее краткая формулировка. Человеку необходимо без потерь перевести с левого на правый берег реки волка, козу и капусту. При этом необходимо выполнять следующие четыре правила:
В этой игре нет ничего, что потребовало бы привлечения специальных возможностей Office 2000, достаточно знать VB и интерфейсные объекты. Реализация этой игры может служить хорошим примером создания документа - обложки, то есть такого документа Office 2000, под обложкой которого находится обычный программный проект, написанный в ядре языка VBA, по существу на чистом VB, и не использующий обращений к специальным объектам приложений Office 2000. С другой стороны, реализация этой игры позволяет продемонстрировать создание и работу с визуальными объектами в документах Office 2000. Зачастую, крайне важно иметь видимые образы для используемых объектов документа и разрешать пользователю документа работать с этими образами, производя над ними те или иные манипуляции.
В нашей игре есть 8 основных объектов: Человек, Волк, Коза, Капуста, Река, Левый Берег, Правый Берег, Лодка. Я добавил еще один объект - Акулу, чтобы реку было страшнее переплывать. Эти объекты будут иметь видимые образы, манипулируя которыми игрок добивается достижения поставленной цели, доставить человека, козу, волка и капусту с левого на правый берег. Полем, где будет разворачиваться действие игры, будет обычная форма VBA, она будет образом объекта Река. На реке (форме) я размещу образы других объектов игры. Каждый из этих объектов будет задаваться объектом VBA класса Image из библиотеки классов MSForms.
Пользователь (игрок) будет действовать в этом видимом мире объектов. Для того чтобы посадить путника в лодку, он может щелкнуть по нему левой кнопкой мыши или перетащить путника в лодку. Для того чтобы лодка переправилась на другой берег, опять-таки можно щелкнуть по ней или явно перетащить ее на другой берег при нажатой левой кнопке мыши. Наконец, чтобы высадить всех пассажиров на берег, достаточно щелкнуть по берегу левой кнопкой. С другой стороны, можно высаживать пассажиров по одному, явно перетаскивая их из лодки на берег. Таким образом, пользователь будет являться причиной событий, возникающих в мире объектов нашей игры. В ответ на происходящие события будут вызываться обработчики этих событий.
Перед программистом, реализующим эту игру, стоят две основные задачи:
Первую из этих задач он, скорее всего, как это сделал я, будет решать руками, визуально проектируя интерфейс, для решения второй задачи ему потребуется знание языка VBA и интерфейсных объектов. Знание объектов Office 2000 (объектов Word, Excel, Power Point) ему не понадобится, под какой бы документной обложкой не был реализован этот программный проект.
Сделаем еще одно замечание, касающееся будущей реализации. Поскольку все визуальные объекты игры размещены в одной форме, то и обработчики событий, происходящих с объектами формы, будут размещены в модуле, связанном с этой формой. Поэтому вся реализация игры может быть сосредоточена в одном модуле, модуле, связанном с формой. Я, однако, поступлю в соответствии с собственными рекомендациями, согласно которым, правильная технология состоит в том, чтобы к модулю формы добавить еще и стандартный модуль, в котором реально происходит обработка событий, большинство же обработчиков, находящихся в модуле формы, строятся очень простыми и состоят из вызова соответствующей процедуры стандартного модуля.
Теперь, когда стратегия реализации намечена, можно приступить к ее исполнению. В тот момент, когда я пишу эти строки, форма с игрой находится перед моими глазами, Полагаю, и Вам будет легче понимать мои рассуждения, если Вы будете представлять, как выглядит поле действия и основные объекты игры. Взгляните на соответствующий рисунок:
(рис 6.1) Форма в начальном состоянии в игре "Волк, Коза и Капуста"
Моя цель состоит в проектировании интерфейса нашей игры. Поскольку стратегическое решение уже принято, действие игры будет разворачиваться в диалоговом окне - форме VBA, то и начинать нужно с создания формы. Вот краткий отчет о моих действиях:
Height и Width. Я изменил имя формы, назвав ее WGCForm, и дал заголовок "Wolf, Goat and Cabbage". Главное, я изменил свойство BackColor, выбрав из палитры цвет, подходящий для речной глади. Теперь форма стала похожей на реку. Еще одно серьезное изменение свойств состояло в том, что форму я сделал немодальной, изменив свойство ShowModal. Форма VBA является, как известно, обычным диалоговым окном. Всякое такое окно может быть модальным или немодальным. Из модальных окон нельзя выйти, не закончив диалога. Если же форму сделать немодальной, то это важное свойство формы позволит выходить из нее, не завершив диалога, зани
маться другими делами, а потом возвращаться в форму. В частности, это позволяет во время игры при необходимости читать документ Word или создавать его, как это делаю я, имея форму перед глазами.Boat, и связал с ним соответствующую картинку лодки. Еще один объект Shark с картинкой акулы я разместил в реке. Он введен больше для антуража и как будущий возможный герой при расширении игры. Вместе с тем он будет играть и некоторую полезную роль, позволяя в любой момент начать игру с начала.Picture так, чтобы соответствующая картинка соответствовала образу объекта. Благо с этим теперь особых проблем нет, так как галерея ClipArt, поставляемая с Office 2000, содержит тысячи различных картинок. Проблема состоит лишь в том, чтобы из этого изобилия выбрать подходящую картинку. Напомню, что поиск в галерее ClipArt возможен по ключевому слову. Задайте, например, "goats" для поиска картинки с изображением козы, учитывая, что при задании ключевого слова лучше использовать множественное число. Итак, я нашел картинки, подогнал их под выбранные размеры Image-элементов, установив подходящее значение свойства PictureSizeMode. Мои объекты получили соответствующие имена: Man, Wolf, Goat and Cabbage.Обратите внимание, порядок размещения Image-элементов имеет значение, поскольку при их частичном совмещении один из них (более поздний) перекрывает другой. Поскольку такая ситуация будет иметь место, когда герои игры собираются в лодке, то целесообразно вначале на форме разместить лодку, а изображение человека разместить последним. Результат моих действий показан на предыдущем рисунке. При некотором навыке, когда картинки под рукой, все действие по формированию интерфейса занимает 10 -15 минут. У меня на это ушло больше времени, так как из-за отсутствия способностей к рисованию проектирование интерфейса это не самая сильная моя сторона. И здесь как раз тот самый случай, когда такие задачи должен решать специалист - дизайнер, а не программист.
Теперь, когда проектирование интерфейса закончено, можно приступать ко второй, более интересной части нашей задачи, собственно к программированию нашей игры.
На этом этапе нам придется решить целый ряд небольших задач, возникающих по ходу игры. Вот перечисление некоторых этапов, позволяющее структурировать игру и наши действия по ее реализации:
Будем следовать этой схеме при описании реализации игры.
При проектировании интерфейса были введены образы объектов, с которыми может взаимодействовать конечный пользователь. Теперь пришла пора ввести сами объекты. Обычно это одна из самых серьезных задач, требующая, как правило, введения собственных классов, тщательного продумывания свойств, методов и событий, которыми должны обладать объекты класса. Мы же можем обойтись без особых сложностей, поскольку у нас довольно простая задача. Наши объекты, подчиняясь игроку, выполняют лишь некоторые перемещения, число которых ограниченно. Задача программы следить лишь за тем, чтобы эти перемещения соответствовали небольшому числу правил. Заметьте, что это игра человека с самим собой, а не игра человека с компьютером. Нам нет необходимости в реализации какой-либо
Для реализации игры достаточно ввести 5 переменных, следящих за состоянием перемещаемых объектов: человека, волка, козы, капусты и лодки. Каждый из них может находиться в трех состояниях, быть на левом берегу, правом берегу или в лодке (у объекта Boat всего два состояния). Еще одна переменная будет следить за числом пассажиров в лодке. Вот описания из раздела Declarations стандартного модуля с именем WGCModule, который я создал первым делом:
Option Explicit 'Состояния наших объектов - человека, волка, козы, капусты и лодки Public StateOfMan As String Public StateOfWolf As String Public StateOfGoat As String Public StateOfCabbage As String Public StateOfBoat As String Public CountInBoat As Byte 'Число пассажиров в лодке Public WidthOfRiver 'Ширина реки, используемая при переправе
Приведем теперь процедуру InitialStates, которая задает начальные установки для наших переменных и визуальных объектов:
Public Sub InitialStates() 'Задаем начальные состояния объектов и их образов StateOfMan = "LeftBank" StateOfWolf = "LeftBank" StateOfGoat = "LeftBank" StateOfCabbage = "LeftBank" StateOfBoat = "LeftBank" CountInBoat = 0 With WGCForm .Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10 .Man.Visible = True .Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10 .Wolf.Visible = True .Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10 .Goat.Visible = True .Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10 .Cabbage.Visible = True .Boat.Top = .LeftBank.Top + 130: .Boat.Left = .LeftBank.Left + _ .LeftBank.Width .Boat.Visible = True WidthOfRiver = .Width - .LeftBank.Width - .RightBank.Width _ - .Boat.Width End With End Sub
Процедура InitialStates будет вызываться в ответ на такие события, как инициализация или активация нашей формы:
Private Sub UserForm_Activate() InitialStates End Sub Private Sub UserForm_Initialize() InitialStates End Sub
Рассмотрим вначале самый простой способ посадки пассажира в лодку. Щелчок левой кнопки мыши на видимом объекте образе одного из наших героев будет перемещать его (образ) в лодку. При этом, естественно, для того, чтобы перемещение было допустимым, нужно чтобы герой и лодка находились на одном берегу. Программно перемещение объекта в лодку делается чрезвычайно просто, для этого достаточно изменить свойства Top и Left, дав им новые значения, зависящие от значения этих свойств объекта Boat. Приведем соответствующие обработчики события Click для каждого из героев игры:
Private Sub Man_Click() ManInBoat 'Call IntoBoat(Me.Man, StateOfMan) End Sub Private Sub Wolf_Click() WolfInBoat 'Call IntoBoat(Me.Wolf, StateOfWolf) End Sub Private Sub Goat_Click() GoatInBoat 'Call IntoBoat(Me.Goat, StateOfGoat) End Sub Private Sub Cabbage_Click() CabbageInBoat 'Call IntoBoat(Me.Cabbage, StateOfCabbage) End Sub
Обработчики событий, как я и говорил ранее, очень простые, они вызывают соответствующую процедуру обработки события из стандартного модуля. Я реализовал две стратегии обработки, поэтому в теле обработчика предусмотрен вызов двух различных процедур. Один из этих вызовов закомментирован. Вот тексты процедур, соответствующие действующим (не закомментированным) вызовам:
Public Sub ManInBoat() 'Посадка пассажиров в лодку If StateOfMan = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfMan = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Man.Top = .Boat.Top - 30 .Man.Left = .Boat.Left + 25 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub Public Sub WolfInBoat() 'Посадка пассажиров в лодку If StateOfWolf = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfWolf = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Wolf.Top = .Boat.Top - 5 .Wolf.Left = .Boat.Left + 50 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub Public Sub GoatInBoat() 'Посадка пассажиров в лодку If StateOfGoat = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfGoat = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Goat.Top = .Boat.Top - 20 .Goat.Left = .Boat.Left + 100 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub Public Sub CabbageInBoat() 'Посадка пассажиров в лодку If StateOfCabbage = StateOfBoat Then 'лодка и пассажир на одном берегу StateOfCabbage = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку .Cabbage.Top = .Boat.Top + 5 .Cabbage.Left = .Boat.Left + 5 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub
Я написал четыре почти одинаковые процедуры для размещения каждого из героев в лодке. Каждому из них отведено постоянное место в лодке и координаты подобраны так, чтобы изображение казалось бы более правдоподобным. Однако с программистской точки зрения это решение может показаться не самым удачным, в особенности при большом числе объектов. Поэтому я приведу и второй вариант, когда размещение любого из объектов выполняется одной и той же процедурой, имеющей в этом случае два параметра - видимый образ объекта и его состояние:
Public Sub IntoBoat(Im As Image, ByRef St As String) 'Посадка пассажиров в лодку If St = StateOfBoat Then 'лодка и пассажир на одном берегу St = "InBoat" 'изменяем состояние With WGCForm 'Меняя координаты объекта, перемещаем его в лодку Im.Left = .Boat.Left + 5 + CountInBoat * 50 Im.Top = .Boat.Top - CountInBoat * 30 'увеличиваем число пассажиров CountInBoat = CountInBoat + 1 End With TestingState 'Проверка корректности нового состояния End If End Sub
Заметьте, теперь координаты, которые получает размещаемый объект, зависят не только от расположения лодки, но и от числа пассажиров в ней. Обращение к этой процедуре в обработчике событий закомментировано. При желании можно пользоваться любым из двух приведенных вариантов.
Для ребенка, играющего в эту игру, вряд ли на первых порах будет понятно, почему щелчок по объекту приводит к его перемещению в лодку. Интуитивно, более разумным действием, приводящим к посадке объекта в лодку, было бы прямое перетаскивание объекта в лодку из его текущего положения на берегу. Реализацией такого способа перемещения объекта мы сейчас и займемся. При этом мы не будем тащить сам объект, хотя и это не трудно было бы сделать, а используем технику, основанную на специальном объекте DataObject, входящем в состав библиотеки MSForms. Эта техника хороша тем, что она позволяет опустить перетаскиваемый объект точно в лодку. Всякая попытка опустить объект где-либо в другом месте приведет к неуспеху операции перетаскивания. Момент, когда цель перетаскивания достигнута, и можно отпустить нажатую до этого кнопку мыши, определяется тем, что изменяется внешний вид курсора (появляется значок "+"), что и позволяет точно опустить объект в нужное место. Реализацию этого довольно сложного сп
особа перетаскивания обеспечивают обработчики трех событий. Первое из этих событий связано с самим перетаскиваемым объектом. В тот момент, когда над объектом нажимается левая кнопка мыши с целью начать его перетаскивать или копировать в точку назначения, возникает событие "MouseMove". В обработчике этого события и следует создать новый объект класса DataObject, определить некоторые его свойства и вызвать метод StartDrag. Этот метод работает совсем не так, как большинство обычных методов. Его действие оканчивается в тот момент, когда завершится операция перетаскивания. В качестве результата метод возвращает 0, если операция закончилась неуспехом, и ненулевое значение в противном случае. Заметьте, что во время перетаскивания, то есть еще до того, как StartDrag завершит работу, будут возникать другие события и, следовательно, будут работать другие обработчики событий. Два таких события будут возникать, когда перетаскиваемый объект достигает точки назначения, точнее, области назначения. В этот момент у целевого
объекта возникает событие BeforeDragOver. Обработчик этого события изменяет внешний вид курсора, что является сигналом достижения цели назначения и позволяет отпустить нажатую кнопку мыши. Обработчик второго события у целевого объекта BeforeDropOrPaste и реализует операцию опускания объекта. Если все завершится благополучно, то успехом заканчивает свою работу и метод StartDrag, которому возвращается управление. Подробнее обо всех деталях работы с этим объектом можно прочитать в моей книге, ссылка на соответствующее место в которой была уже сделана.
После всех этих пояснений можно привести и обработчики соответствующих событий, реализующих операции по перетаскиванию объектов в лодку:
Private Sub Man_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Man" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes8 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub Private Sub Wolf_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Wolf" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes9 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub Private Sub Goat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Goat" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes10 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub Private Sub Cabbage_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Cabbage" Effect = MyDataObject.StartDrag If Effect = 0 Then 'перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes11 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub
Заметьте, для четырех перемещаемых объектов: человека, волка, козы и капусты написаны четыре обработчика события MouseMove. Каждый из них создает свой объект DataObject, запоминает в его свойстве Text название перемещаемого объекта и запускает метод StartDrag. Но цель у всех этих объектов одна - лодка. У объекта Boat два обработчика событий, вне зависимости от числа пассажиров лодки. Обработчик события BeforeDragOver для всех перемещаемых объектов один и тот же, поскольку его задача изменить вид курсора при попадании перемещаемого объекта в область назначения. Обработчик события BeforeDropOrPaste более сложный. Он должен произвести разбор случаев и определить, какой именно объект прибыл в точку назначения и соответствующим образом расположить его в лодке. Анализ свойства Text, объекта DataObject, переданного в качестве параметра обработчику событий, позволяет провести разбор случаев. После этих предварительных замечаний приведем тексты самих обработчиков:
Private Sub Boat_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора. Cancel = True Effect = fmDropEffectCopy End Sub Private Sub Boat_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения. Cancel = True Effect = fmDropEffectCopy 'Разбор случае. Анализ прибывшего объекта и вызов процедуры его размещения в лодке If Data.GetText = "Man" Then ManInBoat ElseIf Data.GetText = "Wolf" Then WolfInBoat ElseIf Data.GetText = "Goat" Then GoatInBoat Else: CabbageInBoat End If End Sub
На этом закончим рассмотрение задачи посадки пассажиров в лодку и перейдем к рассмотрению следующей задачи.
По ходу игры лодка с пассажирами должна переезжать с одного берега на другой. И опять-таки, я реализовал два варианта интерфейса. В первом, более простом варианте щелчок по лодке, заставляет ее переправиться на другой берег, если соблюдены все условия игры, в лодке есть человек и никто никого не кушает. В обработчике события Click объекта Boat вызывается соответствующая процедура, которая требуемым образом изменяет координаты, как самой лодки, так и находящихся в ней пассажиров. Вот текст обработчика и вызываемой в нем процедуры Crossing:
Private Sub Boat_Click() 'Переправа на другой берег Crossing End Sub Public Sub Crossing() 'Переправа на другой берег 'Есть ли человек в лодке With WGCForm If StateOfMan = "InBoat" Then If StateOfBoat = "LeftBank" Then 'Едем на правый берег 'Меняем координаты лодки и кормчего StateOfBoat = "RightBank" .Boat.Left = .Boat.Left + WidthOfRiver .Man.Left = .Man.Left + WidthOfRiver 'Анализ присутствующих пассажиров If StateOfWolf = "InBoat" Then .Wolf.Left = .Wolf.Left + WidthOfRiver End If If StateOfGoat = "InBoat" Then .Goat.Left = .Goat.Left + WidthOfRiver End If If StateOfCabbage = "InBoat" Then .Cabbage.Left = .Cabbage.Left + WidthOfRiver End If Else 'Едем на левый берег 'Меняем координаты лодки и кормчего StateOfBoat = "LeftBank" .Boat.Left = .Boat.Left - WidthOfRiver .Man.Left = .Man.Left - WidthOfRiver 'Анализ присутствующих пассажиров If StateOfWolf = "InBoat" Then .Wolf.Left = .Wolf.Left - WidthOfRiver End If If StateOfGoat = "InBoat" Then .Goat.Left = .Goat.Left - WidthOfRiver End If If StateOfCabbage = "InBoat" Then .Cabbage.Left = .Cabbage.Left - WidthOfRiver End If End If End If End With End Sub
Более интересна техника перетаскивания лодки с берега на берега. Ранее лодка служила целью назначения перетаскиваемых в нее объектов. Теперь она сама станет перемещаемым объектом и, следовательно, для нее мы создадим обработчик события MouseMove, в котором и вызовем метод StartDrag объекта DataObject. В роли целевых будут выступать в зависимости от ситуации объекты LeftBank и RightBank, для каждого из которых будут написаны по два обработчика событий BeforeDragOver и BeforeDropOrPaste. О них позже пойдет более подробный разговор, а сейчас для лодки приведем текст обработчика события MouseMove, запускающего процесс перемещения:
Private Sub Boat_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) Dim Effect As Integer 'MyDataObject используется при перетаскивании объектов Dim MyDataObject As DataObject If Button = 1 Then Set MyDataObject = New DataObject MyDataObject.SetText "Boat" 'Запоминаем имя перемещаемого объекта Effect = MyDataObject.StartDrag 'запускаем процесс перемещения If Effect = 0 Then перетаскиваемый объект не достиг цели 'Сообщение о неуспехе MsgBox Prompt:=Mes1 + vbCrLf + Mes7 + Mes15 _ + vbCrLf + Mes12, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 End If End If End Sub
Как видите, ничего нового в этом обработчике нет в сравнении с уже рассмотренными подобными обработчиками, все они построены по одной схеме. Больший интерес представляет обработка событий для целевых объектов, представляющих левый или правый берег, куда причаливает лодка. Но эти объекты являются целевыми и при высадке пассажиров на берег, когда они высаживаются (перетаскиванием) на берег. Поэтому отложим их описание до следующего параграфа, где будет рассмотрена высадка пассажиров из лодки.
Эта операция представляет процесс, обратный процессу посадки в лодку. Опять-таки, я реализовал две возможных стратегии. После того, как лодка причалила к берегу, одним щелчком по берегу можно высадить всех пассажиров из лодки. Вторая стратегия соответствует интуитивному поведению, когда пассажиров, не обязательно всех, можно высаживать по одному путем простого их перетаскивания на берег. Рассмотрим вначале более простую стратегию, реализованную в обработчиках событий Click для двух объектов LeftBank и RightBank:
Private Sub LeftBank_Click()
'Все пассажиры из лодки высаживаются на левый берег
ONLeftBank ("All")
End Sub
Private Sub RightBank_Click()
'Все пассажиры из лодки высаживаются на правый берег
OnRightBank ("All")
End Sub
Сами обработчики являются простыми, поскольку вся обработка спрятана в вызываемых процедурах стандартного модуля, которым в качестве параметра передается слово "All", указывающее на необходимость высадки на берег всех пассажиров. Эта же процедура будет использоваться и во второй стратегии, когда пассажиры высаживаются по одному. Давайте перейдем к ее рассмотрению. Для самих перетаскиваемых объектов обработчики события MouseMove, запускающие перетаскивание объекта, уже написаны. Мы рассматривали их, когда речь шла о перемещении объектов с берега в лодку. Теперь изменилась цель перетаскивания из лодки на берег, левый или правый в зависимости от того, куда причалила лодка. Поэтому нам надо лишь рассмотреть обработчики событий для целевых объектов LeftBank и RightBank. Напомним, эти объекты являются целевыми и при причаливании лодки к берегу. Вот тексты этих обработчиков:
Private Sub LeftBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора. Cancel = True Effect = fmDropEffectCopy End Sub Private Sub LeftBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения. Cancel = True Effect = fmDropEffectCopy 'Пассажир высаживается на левый берег или лодка причаливает к нему ONLeftBank (Data.GetText) End Sub Private Sub RightBank_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal DragState As MSForms.fmDragState, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Изменяется внешний вид курсора. Cancel = True Effect = fmDropEffectCopy End Sub Private Sub RightBank_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, ByVal Action As MSForms.fmAction, ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer) 'Достигнута цель перетаскиваемого объекта.Объект можно опустить в точку назначения. Cancel = True Effect = fmDropEffectCopy 'Пассажир высаживается на правый берег или лодка причаливает к нему OnRightBank (Data.GetText) End Sub
Все программистские сложности спрятаны в процедурах стандартного модуля OnLeftBank и OnRightBank. Именно в них предусмотрен разбор случаев, то ли лодка причалила к берегу, то ли человек, то ли какой-либо из его спутников высаживается на берег. Здесь же предусмотрена проверка того, на какой берег в данный момент возможна высадка или причаливание.
Public Sub ONLeftBank(Who As String) If StateOfBoat = "LeftBank" Then 'пассажиров из лодки можно высадить на левый берег With WGCForm If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then 'можно высадить человека .Man.Top = .LeftBank.Top + 15: .Man.Left = .LeftBank.Left + 10 StateOfMan = "LeftBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then 'можно высадить волка .Wolf.Top = .LeftBank.Top + 80: .Wolf.Left = .LeftBank.Left + 10 StateOfWolf = "LeftBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then 'можно высадить козу .Goat.Top = .LeftBank.Top + 140: .Goat.Left = .LeftBank.Left + 10 StateOfGoat = "LeftBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then 'можно высадить капусту .Cabbage.Top = .LeftBank.Top + 200: .Cabbage.Left = .LeftBank.Left + 10 StateOfCabbage = "LeftBank" CountInBoat = CountInBoat - 1 End If End With ElseIf (Who = "Boat") Then 'Лодка переезжает на левый берег Crossing End If TestingState End Sub Public Sub OnRightBank(Who As String) If StateOfBoat = "RightBank" Then 'пассажиров из лодки можно высадить на правый берег With WGCForm If ((Who = "All") Or (Who = "Man")) And (StateOfMan = "InBoat") Then 'можно высадить человека .Man.Top = .RightBank.Top + 15: .Man.Left = .RightBank.Left + 10 StateOfMan = "RightBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Wolf")) And (StateOfWolf = "InBoat") Then 'можно высадить волка .Wolf.Top = .RightBank.Top + 80: .Wolf.Left = .RightBank.Left + 10 StateOfWolf = "RightBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Goat")) And (StateOfGoat = "InBoat") Then 'можно высадить козу .Goat.Top = .RightBank.Top + 140: .Goat.Left = .RightBank.Left + 10 StateOfGoat = "RightBank" CountInBoat = CountInBoat - 1 End If If ((Who = "All") Or (Who = "Cabbage")) And (StateOfCabbage = "InBoat") Then 'можно высадить капусту .Cabbage.Top = .RightBank.Top + 200: .Cabbage.Left = .RightBank.Left + 10 StateOfCabbage = "RightBank" CountInBoat = CountInBoat - 1 End If End With ElseIf (Who = "Boat") Then 'Лодка переезжает на правый берег Crossing End If TestingState End Sub
Нам осталось рассмотреть еще пару важных моментов, завершающих реализацию игры. Всякий раз, когда объекты игры изменяют свое состояние, необходимо проверять, является ли оно допустимым, не может ли съесть волк козу, или коза капусту. Многократно вызываемая по ходу дела процедура TestingState осуществляет все эти проверки:
Public Sub TestingState() 'Тестирование состояний With WGCForm 'Волк съел козу If (StateOfWolf = StateOfGoat) And (StateOfWolf <> StateOfMan) Then .Goat.Visible = False MsgBox Prompt:=Mes1 + vbCrLf + Mes3, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 InitialStates End If 'Коза съела капусту If (StateOfCabbage = StateOfGoat) And (StateOfCabbage <> StateOfMan) Then .Cabbage.Visible = False MsgBox Prompt:=Mes1 + vbCrLf + Mes4, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 InitialStates End If 'Слишком много пассажиров! If (CountInBoat > 2) Then If StateOfMan = "InBoat" Then .Man.Visible = False If StateOfWolf = "InBoat" Then .Wolf.Visible = False If StateOfGoat = "InBoat" Then .Goat.Visible = False If StateOfCabbage = "InBoat" Then .Cabbage.Visible = False MsgBox Prompt:=Mes1 + vbCrLf + Mes5, Buttons:=vbCritical + vbOKOnly, Title:=Mes13 InitialStates End If 'Конец игры - Все пассажиры собрались на правом берегу If (StateOfMan = "RightBank") And (StateOfWolf = "RightBank") And _ (StateOfGoat = "RightBank") And (StateOfCabbage = "RightBank") Then MsgBox Prompt:=Mes2 + vbCrLf + Mes6, Buttons:=vbExclamation + vbOKOnly, Title:=Mes14 End If End With End Sub
При обнаружении критической ситуации, например, когда волк, оставленный без присмотра, съедает козу, образ козы исчезает с игрового поля и выдается соответствующее сообщение о том, что произошло ужасное событие и игра не может быть продолжена. Взгляните, как выглядит эта ситуация:
(рис 6.2) Одна из критических ситуаций в игре "Волк, Коза и Капуста"Заметим, что после нажатия кнопки "OK" в окне выдачи сообщения вызывается процедура InitialStates и игра возвращается в начальное состояние. Скажем еще несколько слов об организации диалога. Все константы, используемые в диалоге, собраны в разделе объявлений стандартного модуля WGCModule:
'Константы для организации диалогов Public Const Mes1 = "Ужасно, нелепо и грустно! " Public Const Mes2 = "Прекрасно, как славно, отлично! " Public Const Mes3 = " Волк съел козу! " Public Const Mes4 = " Коза съела капусту! " Public Const Mes5 = " Лодка перевернулась, и все утонули! " Public Const Mes6 = " Вам это удалось! Все переправились!" Public Const Mes7 = " Не удалось дотащить " Public Const Mes8 = " Человека! " Public Const Mes9 = " Волка! " Public Const Mes10 = " Козу! " Public Const Mes11 = " Капусту! " Public Const Mes12 = " Повторите попытку! " Public Const Mes13 = " Беда!" Public Const Mes14 = " Радость! " Public Const Mes15 = " Лодку! "
Такой подход позволяет при необходимости легко перейти на другой язык диалога, или сделать выбор языка параметром игры. Разумно также, но я не стал этого делать, собрать все диалоги в специальной процедуре Dialogs. Я отказался также от использования объекта при ведении диалога, хотя он очень подходит для подобных игр. Последнее решение обусловлено тем, что мне хотелось в чистом виде сохранить концепцию документа - обложки, а объект является специфическим объектом Office 2000. Его использование не позволило бы перенести игру без всяких хлопот в чистый VB.
Игра заканчивается, когда играющему удается перевезти всех путников на правый берег, и по этому случаю выдается радостное сообщение. Вот как оно выглядит:
(рис 6.3) Окончание игры "Волк, Коза и Капуста"Чтобы начать игру с начала, достаточно щелкнуть по объекту Shark, который до сих пор никак еще не использовался. В ответ на событие Click вызывается процедура InitialStates:
Private Sub Shark_Click() InitialStates End Sub
Для того чтобы игра начиналась с открытием документа - обложки, зададим обработку события Open для этого документа:
Private Sub Document_Open() WGCForm.Show End Sub
На этом я завершаю описание реализации этой игры. Конечно, моя цель состояла не в том, чтобы создать совершенную реализацию данной игры. Реализация игры, скорее побочный продукт моей работы. Я выбрал эту простую игру в качестве хорошего примера, на котором я постарался показать, как создаются документы - обложки, как можно создать и использовать визуальные объекты. Что же касается самой игры, то ее несомненно можно усовершенствовать. Вот несколько возможных направлений работы для тех, кто хотел бы довести эту игру до "товарного" вида:
Рассмотрим еще один вопрос, не связанный напрямую с реализацией игры, но относящиеся к нашей теме.
Я уже говорил, что одно из достоинств документов типа "обложка", что они сравнительно легко переносятся в другие программные среды. Проще всего, программный проект, сделанный на VBA и находящийся под обложкой одного из документов Office 2000, перенести в чистый VB. Для этого не потребуется почти никаких усилий. Этим мы сейчас и займемся. Наш программный проект состоит из одной формы WGCForm и одного стандартного модуля WGCModule. Нет никаких проблем, чтобы экспортировать эту форму и модуль, сохранив их в виде файлов с уточнениями frm и соответственно. Для этого достаточно выбрать пункт ExportFile из главного меню File или из контекстного меню, доступного при нажатии правой кнопки на модуле или форме в окне проекта. После того, как форма и модуль сохранены, они могут быть импортированы в любой из документов Office 2000, где начнут работать под другой обложкой. Более интересно, что точно также они могут быть импортированы в чистый VB, что я и сделал. Взгляните, как выглядит проект VB после переноса в
него нашей формы и модуля.
(рис 6.4) Проект WGC, перенесенный в среду VB6 Следует обратить внимание на то, что формы VB и VBA это все-таки разные формы для VB. Формы VBA загружаются в среде VB специальным загрузчиком таких форм ActiveX Designer MSForms. Этот дизайнер включен во все версии VB6, поэтому на компьютере достаточно иметь VB и нет необходимости в существовании Office 2000. Подобно всем другим дизайнерам VB он имеет собственную динамически подключаемую библиотеку (run time dll). Это приводит к увеличению накладных расходов, но сам перенос осуществляется без проблем. Теперь, имея проект на VB, можно использовать все преимущества этой среды, сохранив наш проект, например, в виде исполняемого exe - файла, в виде DLL или в виде ActiveX Control.
Следует сделать одно важное замечание, связанное с переносом в VB. Мне все-таки пришлось внести одно изменение в программный текст, прежде, чем проект заработал. Это изменение связано с объектом DataObject. Дело в том, что в VBA - проекте я использовал этот объект, не уточнив его полное имя. По умолчанию предполагалось, что речь идет об объекте DataObject библиотеки MSForms. Когда же проект стал компилироваться в чистом VB, то по умолчанию этот объект стал восприниматься, как объект VB и здесь возникла некоторая путаница. Хотя язык VB позволяет работать с визуальными объектами библиотеки MSForms, он имеет и собственную, весьма похожую библиотеку визуальных объектов. Замечу, что объект DataObject существует в этой библиотеке и, выполняя практически те же функции, является более мощным объектом. Вместо методов GetText и SetText он обладает методами GetData и SetData, способными передавать не только текст, но и данные более сложной структуры, в том числе метафайлы и графические файлы с уточнением bmp. Конеч
но, я не стал переписывать текст, работая с новым, более мощным объектом VB. Я всюду в тексте заменил DataObject на полное имя MSForms.DataObject. После чего все заработало, как нужно.
Несколько слов об обратном переносе программных проектов из VB в VBA. Такой перенос возможен, хотя выполняется сложнее. Прежде всего, замечу, что формы VB не переносятся напрямую в VBA. Для того, чтобы из VB перенести форму, необходимо разрабатывать ее, используя ActiveX Designer MSForms. Ну и, конечно, нужно следить за тем, чтобы реализация не использовала такие объекты VB, для которых нет точных аналогов в VBA, например, не следует в реализации использовать возможности объекта DataObject, принадлежащего чистому VB.
Я уже говорил о том, что офисному программисту приходится начинать свою работу над документом не с нуля. Зачастую, документ в значительной своей части создан вручную и обладает широким спектром стандартных возможностей. Задача программиста состоит в том, чтобы придать документу новые возможности, учитывающие специфику решаемых задач. Документы, созданные вручную и не требующие программирования или требующие его лишь в малой степени, я и называю документами типа "ручная работа". Хотя с позиций офисного программирования такие документы представляют крайний, предельный случай и менее интересны, чем "настоящие", "сшитые на заказ" документы, но нужно отчетливо понимать, что документы " ручной работы" широко распространены. Чаще всего такие документы создают сами пользователи, но иногда эту работу необходимо выполнять и программисту. Созданию и работе с такими документами посвящено большое количество книг и статей, адресованных пользователям. Это отдельная и большая тема, выходящая за пределы рассматриваемых мной вопросов, но полностью обойтись без их рассмотрения не удается, поскольку это естественная часть работы офисного программиста. Поэтому я хочу рассмотреть один пример создания документа подобного рода, а главное, поговорить о том, что для таких документов иногда возможно создание программного проекта без явного программирования, используя возможности такого средства как упоминавшийся мной MacroRecorder.
Разработка электронной формы бланка заказа.
В офисе работают с людьми и с документами. Оставим в стороне людей и займемся документами. В современном офисе документы имеют электронную форму. Давайте рассмотрим все этапы создания документа в электронной форме на примере бланка "СЧЕТ-ФАКТУРА" - типичного документа многих офисов. Будем создавать этот документ вручную, не прибегая к программированию, используя только стандартные возможности Office 2000. Конечно же, используя программирование на VBA, этот документ можно сделать куда более содержательным, например, связав его с базой данных. Но отложим рассмотрение этих вопросов на будущее и сосредоточимся на работе вручную и автоматическому получению макросов, запуск которых будет повторять работу, которую мы будем делать руками.
Прежде, чем браться за дело, дадим несколько общих советов, которыми следует руководствоваться при разработке электронных бланков:
Следуя этим советам, я создал эскиз бланка "СЧЕТ-ФАКТУРА". Конечно, для таких бланков есть зафиксированный стандарт. Я не стал строго следовать этому стандарту, поскольку преследую другие цели. Тем не менее, возможно, что бланк, который будет построен, может послужить шаблоном для создания "настоящего" бланка "СЧЕТ-ФАКТУРА". Наш бланк будет содержать:
(рис 6.5) Эскиз электронного бланкаКонечно, внимательный читатель понял, что перед ним не "бумажный" рисунок, а реальный документ, разработанный средствами Office. Но в каком из приложений он создан? Мог ли он догадаться, что перед ним рабочая страница Excel? Аналогичный бланк можно было бы создать и в других приложениях Office таких, как Word и Access. Сейчас я подробно опишу, как был создан в Excel подобный шаблон, отступая, возможно, в некоторых деталях, так что окончательный результат будет немного отличаться от показанного на рисунке.
Разработка шапки электронного бланка
Как уже говорилось, шапка бланка будет включать логотип офиса, его название и другие реквизиты. Так как шапка может применяться при создании разных бланков, стоит разработать для нее отдельный шаблон. Более того, используя Macrorecorder, будет создан макрос, строящий эту шапку. Сразу же замечу, что это будет не один макрос, а серия небольших макросов, отражающих отдельные этапы работы. Я полагаю, что это правильная технология использования MacroRecorder. Макросы должны быть небольшими и соответствовать отдельным функциям, которые можно выделить естественным образом. В этом случае набор таких макросов допускает переиспользование и процесс создания нового шаблона во многом может свестись к сборке из набора готовых макросов. Мы пойдем по этой дороге, и созданные макросы помогут нам проследить за каждым шагом работы вручную, даже если мы и не опишем подробно его на словах.
Выделим следующие этапы в создании шапки:
Обычный лист Excel содержит "лишние" для бланка детали. Пользователь, работающий с бланком, может и не подозревать, что он работает с рабочим листом Excel, он должен видеть перед собой "обычный" лист с именованными полями, в которые заносится нужная информация. Поэтому я отключил такие элементы рабочего листа, как сетку, выделяющую ячейки, заголовки столбцов и строк, а также строку листа Excel, в которой отображаются имена ячеек и формулы, записанные в них. Это достигается установкой соответствующих переключателей в окне Options (Параметры) из меню Tools (Сервис). Следующим шагом работы является выделение области ячеек, соответствующих размеру электронного бланка и выделение ее подходящим цветом фона. На этом этапе я использовал возможности, предоставляемые для задания фона в окне Patterns (Вид) при выборе команды Cells (Ячейки) из меню Format (Формат). Замечу, что хотя совершенно естественно начинать описание процесса создания бланка с его форматирования, реально форматирование следует выполнять на п оследнем этапе, когда бланк полностью готов и осталось убрать лишние детали.
Итак, вот моя программа действий по форматированию бланка с записью соответствующего макроса:
Приведу текст полученного макроса с добавленными мной комментариями:
Sub ФорматированиеБланка()
'
' ФорматированиеБланка Macro
' Macro recorded 27.11.1999 by Vladimir Billig
' Этот макрос удаляет сетку, заголовки строк и столбцов, строку формул,
'показ нулей в ячейках рабочего листа. Выделяет подходящим фоном рабочую
'область бланка. В данном примере: A1:K56
Range("A1:K56").Select
With Selection.Interior
.ColorIndex = 15 'серый цвет
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
End With
With ActiveWindow
.DisplayGridlines = False 'сетка
.DisplayHeadings = False 'заголовки
.DisplayFormulas = False 'показ формул
.DisplayZeros = False 'отображение нулей
End With
Application.DisplayStatusBar = False 'строка статуса
End Sub
MacroRecorder помещает все создаваемые макросы в отдельный модуль, который я переименовал, назвав его "БланкСчетФактура".
Каждый приличный офис имеет логотип. Наш офис, который мы будем называть "Родная Речь" такой логотип имеет. Под логотипом мы понимаем графический образ, являющийся символом организации и размещаемый на ее бланках. Допустим, рисунок логотипа уже создан, и нам остается только поместить его в нужном месте бланка, возможно, согласовав размеры. Есть две возможности размещения его на бланке. При создании логотипа офиса "РР" я нашел рисунок бабочки и пририсовал в графическом редакторе на каждом из ее крылышек букву "Р", затем привел рисунок к нужным размерам. Чтобы поместить его на бланк, можно вставить рисунок в ячейку рабочего листа. Для этого нужно выделить подходящую ячейку и указать имя файла в окне, раскрывающемся при выборе команды From File (Из Файла) пункта Picture (Рисунок) меню Insert (Вставить). Но лучше идти более сложным путем: разместить на бланке элемент управления "Image", а затем, используя свойство Picture этого элемента, указать, где хранится рисунок. Несомненным преимуществом такого подхода является возможность использования рисунка произвольного размера. Меняя его свойства, можно его растянуть или сжать до приемлемых размеров. Кроме того, этот элемент управления реагирует на события и можно написать процедуру, которая, например, будет выдавать дополнительную информацию, скажем, о "главных" людях офиса, в тот момент, когда пользователь щелкнет кнопкой по области, занятой логотипом. Обращаем Ваше внимание на то, что при помещении на рабочий лист Excel элемента управления, он вставляется как OLE-объект и становится частью коллекции OLEObjects.
Я выбрал путь, использующий OLE-объекты. Мои действия записывал макрос, который был назван "Логотип":
Sub Логотип() ' ' Логотип Macro ' Macro recorded 27.11.1999 by Vladimir Billig 'Этот макрос создает элемент управления Image 'и помещает его в нужную позицию рабочего листа. ActiveSheet.OLEObjects.Add(ClassType:="Forms.Image.1", Link:=False, _ DisplayAsIcon:=False, Left:=23.25, Top:=15, Width:=63, Height:=61.5). _ Select End Sub
Заметьте, MacroRecorder сегодня не умеет следить за действиями, выполняемыми над самим элементом управления, поэтому мои действия по работе с элементом Image не нашли отражения в макросе. Так что каждый раз после запуска макроса работу по заданию свойств элемента управления необходимо повторять заново. Перечислю те изменения, которые я задавал в окне свойств элемента управления
Рядом с логотипом поместим название офиса: "Издательство Родная Речь". Чтобы эта надпись выглядела незаурядно, воспользуемся возможностями, которые предоставляет Shape. Я визуально менял свойства этих объектов, а Macrorecorder фиксировал мои действия в макросе с именем "КрасивоеИмя". Вот текст этого макроса:
Sub КрасивоеИмя() ' ' КрасивоеИмя Macro ' Macro recorded 27.11.1999 by Vladimir Billig 'Этот макрос включает инструментальную панель WordArt 'и с ее помощью создает два рисованных объекта класса Shape 'первый из них задает слово "Издательство", второй -"Родная Речь" ActiveSheet.Shapes.AddTextEffect(msoTextEffect10, "Издательство", _ "Arial Black", 24#, msoFalse, msoFalse, 177.75, 138.75).Select Selection.ShapeRange.IncrementLeft -34.5 Selection.ShapeRange.IncrementTop -126# ActiveSheet.Shapes.AddTextEffect(msoTextEffect4, _ "Р о д н а я Р е ч ь ", _ "Impact", 20#, msoFalse, msoFalse, 197.25, 138.75).Select Selection.ShapeRange.IncrementLeft -30# Selection.ShapeRange.IncrementTop -80.25 End Sub
Заключительный шаг в формировании шапки - заполнение полей с реквизитами офиса: адресом, телефоном и др. Визуально эту операцию выполнить просто. В одной ячейке записывается название реквизита, а в ячейке справа его значение. Обычно одной ячейки для значения не хватает, и требуется расширить это поле до нужных размеров. Чтобы не менять размеры столбцов, проще всего слить идущие подряд ячейки. Вручную для этого нужно выделить ячейки и включить переключатель MergeCells (Объединение ячеек) на вкладке Alignment (Выравнивание) из пункта Cells (Ячейки) меню Format (Формат).
Чтобы объединить поля с реквизитами и придать им некоторую структуру, их, как правило, заключают в рамку. Подходящую рамку можно выбрать из набора готовых Shape объектов, доступ к которым можно получить, включив инструментальную панель Рисование (Drawing) с элементами рисования; одна из ее кнопок - Автофигуры (AutoShapes) - позволяет добраться до базисных Shape-фигур. Когда рамка накладывается на поля с реквизитами, поля перестают быть видимыми. Рамку следует сделать прозрачной. Для этого нужно, выделив ее, щелкнуть правой кнопкой мыши, из контекстного меню выбрать пункт FormatAuto Shape и в появившемся окне в разделе заполнения (Fill) выбрать из списка значение для цвета NoFill (без заполнения). В этот момент при желании можно изменить стиль начертания границ рамки.
Я проделал все описанные выше операции и получил законченную шапку, отвечающую нашему эскизу. Макрос "РеквизитыИРамка" позволит проследить за моими действиями последнего этапа - формирования реквизитов офиса. Заметьте, в завершение шапки я подвел черту, подчеркнув сформированную часть яркой линией.
Sub РеквизитыИРамка()
'
' РеквизитыИРамка Macro
' Macro recorded 27.11.1999 by Vladimir Billig
'Именование полей реквизитов
Range("D9:E9").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Адрес"
Range("D10:E10").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Телефон, Факс"
Range("D11:E11").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Email"
Range("D12:E12").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "ИНН"
'Запись реквизитов
Range("F9:K9").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Москва, ул. Филевская, 15"
Range("F10:J10").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "175-3434, 175-3480"
Range("F11:J11").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "rr@red.ru"
Range("F12:J12").Select
Selection.MergeCells = True
Selection.HorizontalAlignment = xlLeft
ActiveCell.FormulaR1C1 = "198712345"
Range("F9:J12").Select
Selection.Font.Italic = True
'Построение рамки
ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
105#, 96#, 369.75, 63.75).Select
Selection.ShapeRange.Fill.Visible = msoFalse
'Линия отчеркивания
ActiveSheet.Shapes.AddLine(48.75, 189#, 475.5, 189#).Select
Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
Selection.ShapeRange.Line.Visible = msoTrue
Selection.ShapeRange.Line.Style = msoLineThinThin
Selection.ShapeRange.Line.Weight = 3#
End Sub
Макрос получился довольно большой, поскольку он оперирует с большим числом объектов, и для каждого из них перечисляются все свойства, в том числе и устанавливаемые по умолчанию. Я сократил его текст и добавил комментарии. Следует заметить, что это типичная ситуация, когда полученный макрос является основой для программиста, который оптимизирует его текст, редактирует его, вносит добавления и изменения, добиваясь нужного визуального эффекта.
Мы выполнили всю работу по созданию шапки, разделив ее на четыре этапа. На каждом из этих этапов Macrorecorder создал соответствующий макрос. Осталось объединить эти макросы в один, - мы назвали его "Шапка". Он очень прост: последовательно вызывает уже созданные макросы каждого шага:
Sub Шапка() 'Объединение четырех макросов для построения шапки бланка ФорматированиеБланка Логотип КрасивоеИмя РеквизитыИРамка End Sub
Запустив этот макрос на чистом листе, мы получим заполненную шапку. Точнее, почти заполненную, поскольку макрос "Логотип" не записал действий с объектом класса Image по изменению его свойств. Так что нужно приложить некоторые усилия и повторить действия по установлению свойств объекта, чтобы увидеть картинку, связанную с объектом Image. Взгляните на результат работы макроса "Шапка":
(рис 6.6) Построение шапки
Согласно эскизу следующая часть шаблона бланка заказа содержит информацию о покупателе, - реквизиты организации, заказывающей товар. Мы уже имели дело с реквизитами офиса "РР" при построении шапки. При построении реквизитов покупателя можно было бы в качестве образца воспользоваться макросом "РеквизитыИРамка", слегка подкорректировав его. В каком-то смысле данная задача даже проще, поскольку не нужно заполнять значения полей, задающих реквизиты. Эту работу делает менеджер в момент оформления заказа. Уточним программу действий:
Вот текст соответствующего макроса, записавшего мои действия:
Sub РеквизитыЗаказчика()
'
' РеквизитыЗаказчика Macro
' Macro recorded 28.11.1999 by Vladimir Billig
' Именование бланка
Range("C16:I16").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "СЧЕТ-ФАКТУРА № от "
With ActiveCell.Characters(Start:=1, Length:=31).Font
.FontStyle = "Полужирный"
.Size = 14
End With
With ActiveCell.Characters(Start:=32).Font
.FontStyle = "Полужирный Курсив"
.Size = 11
End With
'Задание полей реквизитов заказчика
Range("B19:C19").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Покупатель"
Range("B20:C20").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Адрес"
Range("B21:C21").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Тел., Факс, Email"
Range("B22:C22").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "ИНН"
Range("D19:J19").Select
Selection.MergeCells = True
Range("D20:J20").Select
Selection.MergeCells = True
Range("D21:J21").Select
Selection.MergeCells = True
Range("D22:J22").Select
Selection.MergeCells = True
'Создание рамки
ActiveSheet.Shapes.AddShape(msoShapeRoundedRectangle, _
34.5, 231.75, 452.25, 63.75).Select
Selection.ShapeRange.Fill.Visible = msoFalse
'Надпись на рамке
ActiveSheet.Shapes.AddTextbox(msoTextOrientationHorizontal, _
95, 225, 60, 15).Select
Selection.Characters.Text = "Покупатель"
'Реквизиты грузоотправителя и грузополучателя
Range("B25:D25").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Грузоотправитель и его адрес"
Range("E25:J25").Select
Selection.MergeCells = True
Range("B26:D26").Select
Selection.MergeCells = True
ActiveCell.FormulaR1C1 = "Грузополучатель и его адрес"
Range("E26:J26").Select
Selection.MergeCells = True
Range("B25:J26").Select
Selection.Font.FontStyle = "Полужирный Курсив"
Selection.Font.Size = 9
'Отчеркивание
ActiveSheet.Shapes.AddLine(44.25, 357.75, 500.25, 357.75).Select
Selection.ShapeRange.Line.Style = msoLineThinThin
Selection.ShapeRange.Line.Weight = 3#
Selection.ShapeRange.Line.ForeColor.SchemeColor = 48
Selection.ShapeRange.Line.Visible = msoTrue
End Sub
Запустив макрос "РеквизитыЗаказчика", на рабочем листе с уже созданной шапкой я получил следующую часть бланка:
(рис 6.7) Шапка и раздел с реквизитами заказчика
Основная часть этого бланка - таблица со сведениями о заказываемых товарах. Сетка, которая обычно очерчивает границы ячеек рабочего листа, была удалена, на электронном бланке заказа она неуместна. Теперь необходимо восстановить некоторые из этих границ, чтобы нарисовать таблицу в привычной для глаз форме. На этом этапе я буду работать с вкладкой Borders ("Границы"), открываемой в окне Format Cells ("Формат ячеек") из меню Format ("Формат"). С объектной точки зрения границы объекта класса Range составляют коллекцию Borders. Меняя свойства элементов этой коллекции (объектов Border), можно добиться нужного эффекта. Я построю таблицу в три этапа:
Такое разделение сделает обозримыми макросы, транслирующие мои действия в тексты на VBA.
Построение столбцов
Столбцы таблицы это ее поля. Размер поля, его ширина зависит от содержания поля. В Excel требуемого размера можно достичь двумя путями объединением (слиянием) ячеек, составляющих одно поле, или изменением размера соответствующего столбца Excel, отведенного для поля. Поскольку второй способ действует на всю таблицу и может привести к изменению внешнего вида уже сформатированного листа, то применять его следует с определенной осторожностью. При применении такого способа рекомендуется начинать форматирование документа с создания таблицы и соответствующего изменения размеров ее столбцов. В данном случае применяются оба способа, изменяются размеры нескольких столбцов и сливаются ячейки для поля, задающего название товара.
Для решения задачи я выделил первую строку таблицы, слиянием ячеек и передвижкой границы между столбцами добился нужных размеров полей таблицы, а затем, используя вкладку "Границы", выделил внешние и внутренние вертикальные границы. После чего осталось скопировать формат этой строки на нужное количество строк таблицы. Вот макрос, выполняющий эти действия:
Sub СтолбцыТаблицы()
'
' СтолбцыТаблицы Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'Изменение ширины полей, меняя размеры столбцов
Columns("E:E").ColumnWidth = 4
Columns("F:F").ColumnWidth = 4
Columns("I:I").ColumnWidth = 4.43
Columns("K:K").ColumnWidth = 11.86
'Слияние ячеек для поля Название Товара
Range("A32:D32").Select
Selection.MergeCells = True
'Выделение внешних границ: слева,снизу, справа
Range("A32:K32").Select
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.Weight = xlThin
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
.Weight = xlThin
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.Weight = xlThin
End With
'Выделение вертикальной внутренней границы
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin
End With
'Копирование формата на всю область таблицы
Selection.Copy
Range("A33:K46").Select
Selection.PasteSpecial Paste:=xlFormats, Operation:=xlNone, _
SkipBlanks:=False, Transpose:=False
Application.CutCopyMode = False
End Sub
Шапка таблицы
Шапка таблицы будет состоять из двух строк, в первой содержатся названия полей, во второй их индексы. Используя соответствующие атрибуты на вкладке Alignment (Выравнивание), я задал центрирование текста по вертикали и горизонтали, а также автоматическое изменение высоты строки, чтобы текст названия полей был полностью видимым. Кроме того, я выделил графически границы шапки, задав их двойными линиями. Макрос получается, конечно, большим, поскольку оперирует с большим числом объектов. Вот его текст:
Sub ШапкаТаблицы()
'
' ШапкаТаблицы Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
'Именование полей таблицы и задание индексов
Range("A32:D32").Select
ActiveCell.FormulaR1C1 = "Наименование товара"
Range("E32").Select
ActiveCell.FormulaR1C1 = "Единица измерения"
Range("F32").Select
ActiveCell.FormulaR1C1 = "Количество"
Range("G32").Select
ActiveCell.FormulaR1C1 = "Цена"
Range("H32").Select
ActiveCell.FormulaR1C1 = "Сумма"
Range("I32").Select
ActiveCell.FormulaR1C1 = "Ставка НДС"
Range("J32").Select
ActiveCell.FormulaR1C1 = "Сумма НДС"
Range("K32").Select
ActiveCell.FormulaR1C1 = "Всего с НДС"
Range("A33:D33").Select
ActiveCell.FormulaR1C1 = "1"
Range("E33").Select
ActiveCell.FormulaR1C1 = "2"
Range("F33").Select
ActiveCell.FormulaR1C1 = "3"
Range("G33").Select
ActiveCell.FormulaR1C1 = "4"
Range("H33").Select
ActiveCell.FormulaR1C1 = "5"
Range("I33").Select
ActiveCell.FormulaR1C1 = "6"
Range("J33").Select
ActiveCell.FormulaR1C1 = "7"
Range("K33").Select
ActiveCell.FormulaR1C1 = "8"
'Центрирование текста и изменение высоты строки,
'обеспечивающее видимость текста
Range("A32:K33").Select
With Selection
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = True
End With
'Выделение границ шапки
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin
End With
End Sub
Последняя строка
Последняя строка, как и строки шапки, отличается от остальных строк таблицы. Она используется для подведения итогов. Также как и шапку, я выделил ее границы графически. Итак, макрос "ПоследняяСтрока":
Sub ПоследняяСтрока()
'
' ПоследняяСтрока Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
'Задание итоговой строки
Range("A46:D46").Select
ActiveCell.FormulaR1C1 = "Всего к оплате"
Range("A46:K46").Select
With Selection.Font
.Name = "Arial"
.FontStyle = "Полужирный"
.Size = 11
End With
'Выделение границ итоговой строки
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlDouble
.Weight = xlThick
End With
With Selection.Borders(xlInsideVertical)
.LineStyle = xlContinuous
.Weight = xlThin
End With
End Sub
Задание расчетных формул и форматирование полей
Одно из преимуществ построения таблиц в Excel состоит в том, что можно задать формулы для автоматического подсчета значений некоторых полей таблицы. В нашем случае все поля, задающие суммы, будут вычисляться по формулам. Формулы для расчета сумм достаточно очевидны и я не буду их выписывать. В тексте макроса они приведены. При работе вручную я в соответствующих столбцах для сумм задал эти формулы в первой рабочей строке таблицы, а затем скопировал их на оставшиеся рабочие строки. В строке итогов я задал суммирование по столбцам таблицы. Для столбцов таблицы я задал также подходящее форматирование данных. Вот текст макроса "Расчеты":
Sub Расчеты()
'
' Расчеты Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
'Форматирование полей и задание расчетных формул
'Форматирование поля "Название товара"
Range("A34:D45").Select
Selection.ShrinkToFit = True
'Форматирование поля "Единица Измерения"
Range("E34:E45").Select
Selection.HorizontalAlignment = xlCenter
'Форматирование поля "Цена"
Range("G34:G45").Select
Selection.NumberFormat = "0.00"
'Форматирование поля "Сумма"
Range("H34:H46").Select
Selection.NumberFormat = "0.00"
'Формула: Сумма = Цена * Количество
Range("H34").Select
ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
'Копирование формулы
Range("H34").Select
Selection.AutoFill Destination:=Range("H34:H45"), Type:=xlFillDefault
Range("H34:H45").Select
'Итоговая сумма
Range("H46").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
'Форматирование поля "НДС"
Range("I34:I45").Select
Selection.NumberFormat = "0%"
'Форматирование поля "Сумма НДС"
Range("J34:J46").Select
Selection.NumberFormat = "0.00"
'Формула: Сумма НДС = Сумма * НДС
Range("J34").Select
ActiveCell.FormulaR1C1 = "=RC[-2]*RC[-1]"
'Копирование формулы
Range("J34").Select
Selection.AutoFill Destination:=Range("J34:J45"), Type:=xlFillDefault
Range("J34:J45").Select
'Итоговая сумма
Range("J46").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
'Форматирование поля "Всего с НДС"
Range("K34:K46").Select
Selection.NumberFormat = "0.00"
'Формула: Всего с НДС = Сумма + Сумма НДС
Range("K34").Select
ActiveCell.FormulaR1C1 = "=RC[-3]+RC[-1]"
'Копирование формулы
Range("K34").Select
Selection.AutoFill Destination:=Range("K34:K45"), Type:=xlFillDefault
Range("K34:K45").Select
'Итоговая сумма
Range("K46").Select
ActiveCell.FormulaR1C1 = "=SUM(R[-12]C:R[-1]C)"
Range("K47").Select
'Нулевые значения в таблице не отображаются
ActiveWindow.DisplayZeros = False
End Sub
Я собрал макросы, строящие отдельные части таблицы под одной обложкой в макросе "ТаблицаРасчеты":
Sub ТаблицаРасчеты() 'Построение таблицы СтолбцыТаблицы ШапкаТаблицы ПоследняяСтрока Расчеты End Sub
Запустив этот макрос на листе Excel, я получил таблицу, готовую для заполнения. Вот как она выглядит с заполненными двумя строчками. Заметьте, что все расчеты в ней ведутся автоматически.
(рис 6.8) Таблица продажи товаров бланка Счет-ФактураЗаключительный макрос "УтверждающиеПодписи"
Для полноты картины закончим создание бланка утверждающими подписями. Ни в действиях, ни в макросе, записывающем эти действия нет никаких особенностей, о которых стоило бы говорить. Это рутинная работа по помещению текста в ячейки Excel с подходящим для данного случая форматированием этого текста. Приведу текст этого макроса:
Sub УтверждающиеПодписи()
'
' УтверждающиеПодписи Macro
' Macro recorded 29.11.1999 by Vladimir Billig
'
Range("B50:G50").Select
Selection.MergeCells = True
With Selection.Font
.Name = "Arial"
.FontStyle = "Полужирный Курсив"
.Size = 11
End With
ActiveCell.FormulaR1C1 = "Ген. Директор _________________________"
Range("B52:G52").Select
Selection.MergeCells = True
With Selection.Font
.Name = "Arial"
.FontStyle = "Полужирный Курсив"
.Size = 11
End With
Range("B52:G52").Select
ActiveCell.FormulaR1C1 = "Гл. Бухгалтер __________________________"
Range("B55").Select
ActiveCell.FormulaR1C1 = "М. П."
End Sub
Сборка макросов. Макрос "СчетФактура"
Для завершения работы и получения макроса, который строит шаблон бланка "Счет-Фактура", осталось собрать все макросы, строящие отдельные части бланка. Вот текст заключительного макроса:
Sub СчетФактура()
'Этот заключительный макрос строит шаблон бланка Счет-Фактура
'Он вызывает макросы, строящие отдельный части этого бланка
Шапка
РеквизитыЗаказчика
ТаблицаРасчеты
УтверждающиеПодписи
End Sub
Запустив этот макрос на чистом листе Excel, я получил полностью сформированный бланк, который в дальнейшем можно использовать в качестве соответствующего шаблона.
Итак, приведен пример построения документа "ручной работы". Параллельно создан макрос (группа макросов), позволяющий почти полностью повторить эту работу программно. Давайте обсудим, что было сделано, подведем итоги и обсудим перспективы развития подобных документов:
При создании в Excel бланка "Счет-Фактура" MacroRecorder позволил получил макросы, записавшие практически все действия пользователя, создававшего этот бланк вручную. Единственное, что не позволил MacroRecorder, это проследить за действиями при работе с элементом управления, помещенным на бланк. Я уже говорил о том, что программисту полезно обращаться к MacroRecorder не только для того, чтобы получить соответствующий макрос, который он, возможно, использует в дальнейшем, чтобы не повторять действия вручную. Часто он обращается к MacroRecorder в процессе обучения работе с объектами Office 2000. В тех случаях, когда программист не знает с каким объектом ему лучше работать при решении той или иной задачи, какими свойствами и методами объекта следует пользоваться, то часто полезно обратиться к MacroRecorder, записать макрос и посмотреть, как MacroRecorder решает данную задачу. Например, задачей программиста является создание системы, позволяющей проводить оптимизацию инвестиций. Чтобы понять, как и с какими объектами Excel следует работать в данной ситуации, рекомендуется решить вручную несколько оптимизационных задач, записав при этом соответствующие макросы.
(рис 6.9) Построение таблицы в документе Word MacroRecorder в Office 2000 не позволяет записать действия по созданию таблиц в документе Word, хотя, конечно же, есть объект Table и возможно с его помощью программное создание таблиц.
Не стоит особенно печалиться по поводу сравнительно небольших возможностей MacroRecorder. Это лишь один из вспомогательных инструментов, используемых в офисном программировании. Главное, что есть в распоряжении программиста, работающего в среде Office 2000, - язык VBA, набор объектов из библиотек Office 2000, возможность использовать компоненты, созданные на других языках и в других программных средах, используя все эти средства можно решить практически все задачи, возникающие в жизни офиса при работе с его документами. На этом я и закончу введение в офисное программирование.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.