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