Введение в программирование на примере VBA
Часть III. Создание объекта на основе класса
Занятие 15. Создание компонента-таймераЦель занятие – создание компонента-таймера, управляющего «основным» приложением. Цикл 2Для упорядочения наших представлений о дальнейшей работе схематично подытожим сделанное. Мы имеем вполне самостоятельный компонент (несмотря на то, что это – главная форма приложения, правомерно назвать ее компонентом), выполняющий некоторые действия и законченный логически. Но для реализации требуемой функциональности нужен другой компонент. Наша задача – определить «точки соприкосновения» созданного и проектируемого компонентов. Созданный компонент-форма содержит функцию, выполняющую действия (по выводу картинки). Уже упоминалось, что нецелесообразно выполнять функцию по «указке» этого же компонента (формы), потому что тогда основное приложение будет «перегружено» несвойственной ему функциональностью. Иначе говоря, основное приложение должно выводить картинки – и, возможно, делать что-то еще, но не определять момент вывода! АнализНаиболее рационально будет убрать функциональность отсчета времени из основного приложения, перенеся ее в другой компонент, этот другой компонент будет самостоятельно отслеживать время, сообщая основному приложению о наступлении нужного момента. Подцикл 1. Создание дополнительного компонентаСоздание приложения обычно начинается с определения механизма взаимодействия с пользователем – визуального интерфейса. Аналогично этому конструирование компонента следует начинать с механизма взаимодействия с другими компонентами приложения – программного интерфейса. Проектирование программного интерфейсаНачнем с уточнения деталей – как взаимодействует «основное» приложение и вновь создаваемый компонент.
Не правда ли, фраза «оповещение о наступлении нужного момента времени» вполне синонимична выражению «оповещение о наступлении события»? Именно событие мы будем применять для этого. Кроме того, у основного приложения уже есть два контрола, предназначенных для запуска и остановки процесса – кнопки cmdStart и cmdStop. Следует предусмотреть соответствующие атрибуты у создаваемого компонента:
А теперь определим, какие разновидности атрибутов будут определять подобные отношения компонентов.
Подобное «направление» атрибута говорит о том, что <компонент> вызывает метод другого компонента. Если такой вызов делается не программистом путем явного указания в коде, а программой, то это – сообщение о событии. Обратное направление атрибута:
говорит о том, что <компонент> будет выполнять метод или обрабатывать событие. Как вы уже видели, принципиальной разницы между методом и обработчиком события в VBA нет. Согласно этим схемам составим списки атрибутов приложения и будущего компонента: Приложение должно обладать такими атрибутами:
А будущий компонент будет иметь:
Еще раз остановимся на различии между генерацией события и его обработкой. Обработка события производится «принимающим» компонентом, тем, кто оповещается. Событие же генерируется «оповещающим» компонентом, тем, кто оповещает. Хотя это и будет несколько нарушать последовательность цикличности разработки, добавим еще один атрибут – продолжительность интервала времени. Очевидно, что, как и любой атрибут, определяющий качественные и количественные характеристики объекта, это – свойство. В общем случае не следует добавлять функциональность приложению во время проектирования и реализации. Надо было предусмотреть это раньше, при выдвижении требований. Но в данном случае добавляемый атрибут очевидно нужен, просто мы забыли о нем раньше, да и приложение наше настолько простое, что сложностей не будет. Таким образом мы составили списки атрибутов, при посредстве которых два компонента приложения – основная форма, то есть собственно приложение, и дополнительный компонент, – будут взаимодействовать друг с другом. Подобный список атрибутов описывает программный интерфейс компонента. Добавив новый атрибут, получаем описания интерфейса: Основное приложение.
Компонент:
Следует составлять такие списки для создаваемых вами компонентов и сохранять их все время существования приложения – то есть до тех пор, пока вашей программой кто-то пользуется. Если вы захотите когда-либо изменить функциональность компонентов, следует дополнять интерфейсы, но не изменять существующие. Особенно это относится к отдельно существующим компонентам (к сожалению, VBA не позволяет создавать такие), но и в других случаях следует стремиться к неизменяемости интерфейсов. Причина этому такая. Предположим, вы создали какой-то компонент и приложение, использующее его, и кто-то стал успешно использовать ваш продукт. Затем вы улучшили приложение (или компонент), и необдуманно изменили интерфейс. Пользователю придется обновлять и приложение и компоненты, связанные с ним – а набор может быть большим. Если же вы правильно отнесетесь к идее неизменности интерфейса, то обновленный компонент сможет обслуживать старое приложение, просто приложение не будет использовать новую, добавленную функциональность компонента. Вернемся к интерфейсам нашей программы. Для полноты картины следует определить размерность свойства и назначить имена всем атрибутам. Интервалы времени, как правило, отсчитываются в минутах, секундах, часах – то есть в целых величинах. Отрицательное значение интервала бессмысленно. Слишком большой интервал практически бесполезен – вы выключите компьютер и уйдете спать раньше, чем наступит нужный момент. Вполне достаточен был бы тип свойства Byte – целое число от 0 до 255. Но для простоты назначим тип Integer, размерностью от (примерно) –32000 до (примерно) 32000. Отрицательные значения использовать не будем. Определимся с именами атрибутов. Очевидно, что основное приложение лишь управляет атрибутами компонента, поэтому и имен для основного приложения не будет. Оно использует интерфейс, предоставляемый компонентом. Поэтому достаточно определиться с именами атрибутов компонента. Это будет выглядеть так: Компонент: Имя Timer.
Обратите внимание на имя события – Tick. Так как компонент выполняет функции таймера, да и имя компонента Timer – Таймер, то вполне естественно, что он будет «тикать». Остальные имена очевидны.
Не обязательно придерживаться именно такого стиля изображения, схема должна быть понятна прежде всего вам. Обратите внимание на двунаправленность стрелки, символизирующей свойство компонента. Действительно, нет причин для запрета чтения длительности интервала. Мы определились с программным интерфейсом будущего объекта. Класс Сделаем очередное теоретическое отступление и изучим, каким же, собственно, способом мы может создать объект. В своей работе VBA не ограничивается набором объектов, уже содержащихся в нем. Язык VBA имеет средства для создания собственных объектов (следует заметить, что возможности VB гораздо шире). Мы уже знакомились с так называемым пользовательским типом – своеобразной сущностью, содержащей несколько переменных одновременно и работающей с ними как с единым целым. Оказывается, в очень многих языках программирования, в том числе и в VBA, есть сущности, подобные пользовательским типам, но могущие содержать не только данные, но и процедуры или функции. Такие сущности называются классами. Как пользовательский тип VBA выступает основой, своеобразной заготовкой будущих переменных, создаваемых на его основе, так же и класс является особого рода шаблоном, исходя из которого будут создаваться объекты. Ничего совсем неожиданного в понятии класса нет. Формы VBA – тоже классы, имеющие визуальное отображение. К сожалению, VBA не позволит вам создать класс с визуальным отображением, для решения такой задачи следует использовать VB. В нашем случае будущий компонент-таймер и не должен иметь визуального облика, все, что пользователь увидит на экране своего компьютера, мы уже включили в форму. Поэтому VBA – вполне подходящий инструмент для создания такого компонента. РеализацияПриступим к реализации программного интерфейса класса, на основе которого будет создаваться компонент-таймер. Для этого надо «воплотить» в коде описание программного интерфейса, созданное нами раньше. Как вы увидите, сделать это просто – но только в том случае, если была тщательно и скрупулезно выполнена подготовительная работа. Сначала добавим к проекту новый модуль – модуль класса.
Не забудьте, что мы работаем с IDE VBA и сейчас открыт наш проект Multy. Откроется окно кода, а в окне Project Explorer появится новый элемент:
Также изменится содержимое окна Properties:
Сразу изменим имя класса. Будущий компонент будет называться tmrMain (от Timer Main – Главный Таймер). Но это – имя объекта, созданного на основе класса. Имя класса должно быть другим – вспомните, как мы работали с пользовательскими типами, имя типа не совпадало с именем переменной, созданной на его основе. Пусть имя класса будет cTimer (от class Timer).
Реализация программного интерфейса класса Теперь займемся реализацией программного интерфейса – пока без добавления «полезного» кода.
Методы класса
Это делается отдельно для чтения и отдельно для записи свойства, примерно так:
Разберем подробнее, что же мы сделали. В области глобальных объявлений внесена строка: Dim intInterval As Integer Тем самым создается внутренняя переменная intInterval типа Integer. В ней будет храниться значение свойства. Далее: Public Property Get Interval() As Integer Это – свойство для чтения. Когда внешняя программа хочет прочитать значение свойства, она обращается именно к этой конструкции. Ключевое слово – Get – Получить. При этом возвращается то значение, которое хранится в переменной intInterval. Обратите внимание, что указывается тип свойства для чтения. Затем находится код: Public Property Let
Interval(i As Integer) Это – свойство для записи. Когда внешняя программа устанавливает значение свойства, происходит примерно следующее (в псевдокоде): ‘эта строка – в вызывающей программе В свойстве для чтения должен указываться параметр и его тип. Если вы усвоили предыдущий материал, то у вас должен возникнуть вопрос – зачем применять столь сложные конструкции, когда было бы достаточно одной переменной, объявленной публично: Public Interval As Integer Действительно, в простых случаях можно ограничиться и этим. Преимущество процедур свойств – а именно так называется код, созданный нами, – во-первых, в защищенности свойства по сравнению с публичной переменной. Значение свойства сложно «испортить» случайно. Во-вторых, код процедур свойств может выполнять и другие действия, например, проверять полученное значение, изменять другие атрибуты объекта и так далее. Событие Осталось добавить в код класса событие. В VBA это делается способом, для нас еще неизвестным: Событие сперва следует объявить – так же, как объявляется переменная. А уже потом вызвать это событие в коде какого-то метода, примерно так (псевдокод): ‘объявление события При желании можно передавать аргументы: ‘объявление события Подробнее об этом вы можете узнать в справке VBA. Процедура-инициатор события Итак, возникает еще одна сущность, которую следует создать в классе компонента, а именно – процедура, в которой будет инициироваться событие. Это и будет главная процедура класса, обеспечивающая его функциональность. Имя Main – Главный, – зарезервировано языком для особых целей, и назвать так метод класса нельзя. Назовем метод MainCycle – ГлавныйЦикл. Так как в интерфейсе класса нет такого метода, следует объявить его приватным. Событие же объявляется публичным.
Проверим, что же получилось.
В списке компонентов проекта появился элемент cTimer. Такое имя мы присвоили созданному классу.
Справа – список атрибутов класса. Выбирая по очереди каждый атрибут, вы можете ознакомиться с его краткой характеристикой в нижней панели Object Browser, например, так:
Отладка созданного класса пока невозможна, этим мы займемся на следующем этапе. Подцикл 2. Подключение дополнительного компонента к приложениюСледующий этап работы – подключение класса к основному приложения. Создание объекта на основе класса Для того чтобы создать объект на основе класса, следует сначала объявить переменную типа класса, а затем явно инициализировать ее. В этом работа с классами в VBA отличается от всех остальных типов. Эта работа производится на стороне «основного» приложения. Подробнее в псевдокоде: ‘объявление переменной типа класса: Как видите, объявление отличается только добавлением слова WithEvents – С_Событиями, и может размещаться как в коде процедур, так и в области глобальных объявлений. Добавление WithEvents приводит к автоматическому изменению списков в окне кода формы, там появляется новый объект (в левом списке), а при выборе этого объекта – его события (в правом списке). Если класс (или другой подключаемый объект) не содержит событий, то слово WithEvents не нужно. Если вы пропустите это слово при объявлении переменной типа класса, то не сможете обрабатывать события, поступающие от него. Следует также помнить, что, если вы объявили переменную типа класса в какой-нибудь процедуре, то и работать с объектом вы сможете до тех пор, пока не выйдете из этой процедуры. В исполняемом коде должна быть строка явного создания объекта типа класса, начинающаяся словом Set – Установить. Слово New – Новый, – указывает на создание нового объекта. Вот пример: Set <объект> = New <класс> Это значит, что будет создан новый <объект> типа <класс>. Возможен вариант записи без слова New: Set <объект_1> = <объект_2> При этом <объект_1> и <объект_2> – разные имена одного и того же объекта. Новый объект подобным образом не создается. Подобная запись недопустима: Set <объект> = <класс> VBA сообщит об ошибке, увидев подобное. Действительно, создать другое имя для класса нельзя (в VBA, во всяком случае), а указания на создание объекта здесь нет. Будьте внимательны и вносите в код слово New, если вы хотите создать новый объект. Правилом хорошего тона считается явное уничтожение объекта по окончании работы с ним. Это делается присвоением объектной переменной значения Nothing – Ничто. ПроектированиеОпределимся с размещением кода. По-видимому, будет правильным создавать объект таймера при запуске приложения и уничтожать его при выходе из программы. Для таких действий предназначены обработчики событий формы Initialize и Terminate. Первое из них мы уже использовали для инициализации процедуры отображения картинок. Второе событие – Terminate – противоположно по смыслу, выполняется при завершении работы формы. РеализацияПерейдем к реализации того, что мы только что спроектировали.
Option Explicit
Private Sub UserForm_Initialize()
Private Sub UserForm_Terminate() Можно использовать класс. Пока в классе нет полезного кода, проведем тестирование связи основного приложения и объекта. Воспользуемся знакомым нам приемом – функцией MsgBox для вывода простейших сообщений. Добавим MsgBox в методы запуска и останова класса так, чтобы при вызове этих методов появлялись сообщения.
Public Sub StartTimer()
Public Sub StopTimer() Как видите, ничего нового, все это мы уже делали когда-то. Вызовем эти методы из формы, привязав их к нажатиям кнопок:
Private Sub cmdStart_Click()
Private Sub cmdStop_Click() Попробуем проверить, что же получилось. ОтладкаНаконец-то можно приступить к отладке.
Должны появляться окошки с предопределенными нами надписями. Если вы были внимательны – все должно работать.
Тестирование события класса Создадим «обратную» связь события объекта таймера и формы.
Как можно убедиться, просматривая правый список, этот объект имеет лишь одно событие Tick, VBA сразу создает обработчик этого события. Все, как мы и хотели.
Private Sub tmrMain_Tick()
Public Sub StartTimer()
Должно возникнуть окошко, оповещающее нас о получении формой события Tick.
Подцикл 3. Добавление в таймер полезной функциональностиЗадачей следующего этапа разработки будет наполнение класса полезным кодом. Прежде чем проектировать и реализовать код класса, обновим требования к компоненту. Это поможет вспомнить, чего же, собственно, мы хотели достичь с его помощью. ТребованияТаймер через определенные интервалы времени сообщает основной программе о прохождении интервала. Требования подразумевают непрерывную, однообразную работу компонента, начинающуюся с вызова метода StartTimer и продолжающуюся вплоть до обращения к методу StopTimer. О том, следует ли определять свойство Interval до запуска компонента, или можно менять его во время работы, пока не говорим. ПроектированиеПроанализируем требования. Ключевая фраза – непрерывная, однообразная работа. Не правда ли, так можно охарактеризовать известную нам конструкцию – цикл. Выбор технологии Вы знаете о циклах с индексами For...Next и о циклах без индекса Do...Loop. Конструкция цикла с индексом ожидает конечное число повторений, по достижению индексом заранее определенной величины цикл самопроизвольно прекращается. Видимо, для наших целей это не очень подходит. Цикл Do...Loop работает до тех пор, пока не будет получено указание о прекращении работы. Это описание почти буквально подходит под наши требования. Итак, основой компонента будет какая-то разновидность цикла Do...Loop.
Конструирование цикла Перейдем к псевдокоду для большей понятности дальнейшего.
Тогда в псевдокоде: Private Sub MainCycle() То есть с вызовом приватной процедуры MainCycle происходит запуск бесконечного цикла Do...Loop, при каждом повторении которого генерируется событие Tick. У цикла Do...Loop, как вы знаете, есть разновидности, совмещающие цикл с проверкой условия. Условие может проверяться в начале и в конце цикла, может проводиться проверка на соблюдение условия и на выполнение условия. Это мы уже рассматривали ранее. Момент проверки условия определит следующее. Если условие проверяется в начале цикла, то при поступлении команды остановки цикл прекратится. Если же проверка помещена в конце цикла, то даже при команде остановки цикл выполнит еще один «оборот». В нашем случае несущественно, где именно помещать проверку условия. Произвольно поместим проверку в конце цикла. Псевдокод: Private Sub MainCycle() Проверка условия может происходить двояко – при помощи слов языка Until и While. Если цикл создан с использованием слова Until, то он будет работать, пока не будет выполнено условие. Слово While позволяет работу до тех пор, пока условие выполняется. Как видите, принципиальной разницы нет, переформулированием условия можно сделать эти конструкции работающими совершенно одинаково. Тем не менее, нам нужно выбрать. Так как для остановки цикла применяется вызов метода StopTimer, логичным будет предположить, что этот метод что-то изменяет в работе компонента. Значит, для создания цикла естественным будет использование слова Until, прерывающего работу цикла при изменении условия. Псевдокод: Private Sub MainCycle() Раньше при проверке условий мы применяли переменные логического типа – флаги. Нет препятствий к этому и сейчас. Пусть <условие> будет определяться переменной логического типа, именуемой, например, fStop (от flag Stop). Тогда установка флага будет прекращать цикл: Private Sub
MainCycle () В самом цикле мы не предполагаем каких-то действий по изменению этой переменной, а для управления работой цикла у нас уже есть два метода. Очевидно, что остановкой цикла распоряжается метод StopTimer. Псевдокод метода: Public Sub StopTimer() То есть при вызове метода происходит изменение значение флага так, что при очередном проходе цикла условие остановки выполнится и цикл прекратит работу. Для того чтобы переменную fStop можно было изменить в одной процедуре и использовать измененное значение в другой процедуре, переменная должна быть объявлена на уровне модуля класса, то есть в области глобальных объявлений класса. Не забудьте об этом! Вот как это делается: Option Explicit Теперь следует реализовать метод StartTimer. Конструирование метода запуска таймера В этом методе мы будем запускать процедуру MainCycle, как показано в псевдокоде: Public Sub StartTimer() Но это не все. Не было бы смысла в процедуре, не делающей ничего, кроме вызова другой процедуры. Не забывайте, что один раз установленная переменная fStop будет установленной до тех пор, пока вы не измените ее значение самостоятельно – или до конца «жизни» объекта. Поэтому если оставить так, как есть, то после остановки цикла методом StopTimer повторный запуск не удастся. В процедуре StartTimer следует принудительно сбрасывать флаг до того, как будет запущен цикл. Это простое дополнение позволит останавливать и запускать цикл столько, сколько хотите: Public Sub StartTimer()
РеализацияНе делайте запуск. Работа еще не закончена. Реализация таймера согласно схеме: Do имеет два больших недостатка. Недостатки кода VBA Во-первых, здесь нет никакого механизма регулирования «скорости» цикла. Действительно, свойство Interval мы еще никак не использовали. Во-вторых, подобный цикл будет «крутиться» постоянно, занимая ресурсы компьютера впустую. Понятие о WinAPI Для решения этой проблемы недостаточно возможностей VBA. Мы обратимся к набору функций самой Windows. В составе самой операционной системы MS Windows, под управлением которой и работает MS Office, имеется несколько тысяч функций, выполняющих самые разные задачи. Совокупность этих функций называется Windows API – от Application Programming Interface – Интерфейс Прикладного Программирования. Функции Windows API (WinAPI) вполне доступны для использования из VBA. При этом вам придется иметь в виду следующее. Во-первых, использование функций WinAPI не всегда просто, зачастую это потребует «высшего пилотажа» проектирования и написания кода. Отладка программ, написанных с использованием WinAPI, иногда приводит к зависанию как приложения, так и самой операционной системы. Но все неудобства с лихвой компенсируются приобретаемыми возможностями. Во-вторых, в состав справочной системы VBA не входит описание WinAPI – оно слишком объемно. Источники информации вам придется искать самим. Наиболее полное описание вы найдете в справочной системе MSDN, описывающей практически все программные продукты фирмы Microsoft. Но эта библиотека справки распространяется платно, и русского перевода ее нет. Для программистов VB и VBA наилучшим источником сведений будет книга «Win32 API и Visual Basic», написанная Дэном Эпплманом и изданная в русском переводе в 2001 году издательством «Питер». Эта книга, помимо теоретических сведений, содержит описание нескольких сотен функций WinAPI и множество примеров их использования. Большое количество примеров работы с WinAPI вы также найдете в Internet. Для работы с функцией WinAPI ее следует объявить. Синтаксис объявления такой: Declare Sub <имя функции> Lib <”имя библиотеки”> Могут использоваться слова Public/ Private, вместо слова Sub вы можете использовать слово Function – но тогда придется указать тип возвращаемого функцией значения. Подробнее о синтаксисе объявления вы узнаете из справки VBA – поиск по словосочетанию Declare Statement. Функция WinAPI Sleep Так вот, в составе WinAPI есть очень простая по использованию функция, обеспечивающая задержку выполнения программы на указанное при вызове функции время. Эта функция называется Sleep – от Спать. Объявление этой функции выглядит так: Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long) Как видите, это – процедура, обозначенная словом Sub, то есть значение ей не возвращается. Расположена эта процедура в библиотеке Windows, которая называется kernel32.dll, на что указывает слово Lib и последующее имя. Синтаксис объявления процедур Windows API требует явного указания библиотеки, в которой находится процедура, что позволяет использовать несколько одноименных процедур из разных библиотек. Далее следует описание параметра процедуры. Имя параметра – dwMilliseconds, – произвольно, вы можете использовать свое. Используемое при объявлении параметра слово ByVal указывает языку способ обработки аргумента, для вас сейчас это не важно, следует лишь помнить об обязательности указания этого слова при объявлениях процедур/функций WinAPI. Тип параметра процедуры – Long (Длинный). Этот тип соответствует целым числам в диапазоне (примерно) от минус двух миллиардов до двух миллиардов. Тип Long наиболее часто используется в Windows API. Потребность коррекции кода класса – изменение типа свойства интервала Так как ранее мы определили тип свойства Interval как Integer, вам следует внести коррективы в код класса cTimer. Измените тип переменной intInterval на Long, и таким же образом поменяйте тип обоих процедур свойств Interval: Option Explicit Public Property Get
Interval() As Long Мы изменили программный интерфейс, чего делать категорически не рекомендуется. Но в нашем случае это простительно, ведь приложение учебное, а о существовании WinAPI мы не знали. Добавление Sleep к компоненту В общем случае рекомендуется все объявления процедур WinAPI помещать в специально предназначенный для этого модуль. Но так как мы хотим сделать наш компонент-класс пригодным для использования в других проектах, дополнительный модуль без необходимости создавать не стоит.
Option Explicit Мы указали процедуру как Private. Во-первых, в модуле класса нельзя объявлять процедуру WinAPI как Public – язык VBA вам не позволит этого, а во-вторых, нам и не требуется доступность этой функции вне объекта Timer. Теперь решим, где и как будет использоваться функция Sleep. Ее следует поместить в тело цикла Do...Loop для обеспечения задержки при каждом проходе цикла. Нет принципиальной разницы, размещать вызов процедуры Sleep до генерации события Tick, или же после него. Это будет выглядеть так: Private Sub MainCycle() Но мы опять забыли о свойстве Interval. Для обеспечения нужной задержки будет использоваться переменная intInterval, хранящая значение задержки. Следует помнить лишь, что, как явствует из имени параметра процедуры Sleep, это значение трактуется как миллисекунды – то есть тысячные доли секунды. Если вы хотите манипулировать секундами, следует предусмотреть преобразование (умножить на 1000). Есть смысл проводить расчеты при получении значения, то есть в процедуре свойств Property Let Interval. Наш код принял вид: Private Sub MainCycle() Перед тем, как произвести пробный запуск, доведем до конца код формы frmMain.
Private Sub tmrMain_Tick()
Для простоты мы не будет помещать на форму какие-то элементы управления, предназначенные для задания значения интервала – подобное вы сможете сделать сами при дальнейшем усовершенствовании приложения. Зададим значение интервала один раз, при создании объекта в процедуре инициализации формы: Private Sub
UserForm_Initialize() Не забудьте, что значение – в тысячных долях секунды.
Не забывайте о потенциальной опасности отладки приложений, использующих WinAPI. ОтладкаИ вновь отладка.
И тут мы сталкиваемся с проблемой. Мало того, что никаких картинок не видно, так еще и приложение не реагирует на щелчки мыши и нажатия клавиш! Неужели зависло? Остановить отлаживаемое приложение VBA можно нажатием [Ctrl]+[Break].
Приложение перейдет в пошаговый режим, наподобие того, который мы наблюдали раньше при запуске программы нажатием [F8]. Вы можете пройти программу в пошаговом режиме, отслеживая значения переменных, но скажем заранее – все верно. Проблема вот в чем. Цикл Do...Loop занимает ресурсы приложения – в нашем случае Excel, не позволяя программе-субстрату делать какие-то другие действия. В предыдущих наших опытах этот эффект не был заметен, так как все действия выполнялись быстро. Здесь же цикл долгий, и, тем более, замедленный процедурой Sleep. Возникает впечатление зависания приложения. А вывод на экран картинок не происходит по той же причине – для «рисования» картинок требуются ресурсы приложения-субстрата. Оператор DoEvents Решение в том, чтобы во время работы цикла позволить выполнять свои задачи другим программам. Для этого в составе VBA есть оператор DoEvents – от Делай с Событиями. Этот оператор следует поместить в тело цикла Do...Loop, место его размещения большой роли не играет: Private Sub MainCycle()
Теперь все работает! Имейте в виду, что указанное значение интервала ориентировочно. Как вы только что убедились, помимо созданного нами компонента работают и другие приложения, занимая при этом время. Поэтому точный хронограф так вы не сделаете (да в операционной системе MS Windows это и невозможно), а для простой мультипликации возможностей компонента хватит. Следует помнить также о пределах производительности графической системы Windows. Если вы уменьшите значение интервала до, к примеру, одной миллисекунды, то операционная система не будет успевать прорисовывать – и реальная скорость смены кадров будет меньше.
Повторное использование кода в VBAИ последнее. Как вы могли заметить, весь код приложений VBA находится внутри документов приложений-субстратов. Для возможности использования компонента в другом приложении VBA следует переместить код в другой документ. Вы, конечно, можете попросту скопировать весь код и вставить в другой проект, но это не лучшее решение. Среда IDE VBA предоставляет возможность перемещения кода между документами.
Возникнет диалоговое окно, где вам будет предложено место для сохранения файла с кодом класса и имя файла.
Просматривая свой рабочий каталог в проводнике Windows, вы обнаружите новый файл с именем cTimer.cls – в нем и находится код нашего класса. Командой вы сможете произвести обратное действие – вставить содержимое файла в проект VBA.Таким способом в VBA делается попытка решить проблему повторного использования кода. Итог занятияМы прошли последнее занятие учебника, самое сложное и информативное. Для начала составьте конспект:
Теперь остановимся подробнее на некоторых моментах, которые следует уяснить для дальнейшей эффективной работы. Понятие интерфейса – interface (от англ. [Взаимодействие] Между Лицами[-Участниками]), – одно из важных в программировании вообще. Под интерфейсом понимается соглашение о взаимодействии двух сторон, участвующих в этом взаимодействии. Визуальный интерфейс, выступающий частным случаем интерфейса, определяет какие-то видимые сущности и правила обращения с ними для получения определенного результата. Например, пользователь, встретив на экране кнопку- CommandButton, вправе предположить, что при щелчке на этой кнопке приложение совершит какое-то действие. А, увидев, предположим, список- ListBox, пользователь будет искать в нем какие-то сгруппированные значения для того, чтобы сделать выбор. Программный интерфейс, как еще один частный случай интерфейса, описывает правила взаимодействия компонентов программы. Описания объектов, рассмотренные нами в ходе обучения, включающие свойства, методы и события – пример программных интерфейсов этих объектов. Вновь создаваемый объект также должен иметь подобное описание, один из способов составления которого наглядно показан в ходе занятия. Принцип неизменности интерфейса важен всегда, когда есть вероятность распространения приложения среди пользователей – и если автор предполагает дальнейшее улучшение программы. Причина важности этого принципа рассмотрена выше. Чрезвычайно важная концепция, лежащая в основе объектности как таковой – класс. Синтаксис и применение классов довольно подробно рассмотрены в материале занятия. Следует уточнить еще несколько моментов. При создании класса силами VBA одному модулю класса будет всегда соответствовать один класс. То есть нельзя в одном модуле класса разместить несколько классов, и нельзя разбить класс на несколько модулей класса, хотя вполне допускается использование классом каких-то процедур (функций), находящихся в других компонентах программы. Помните лишь, что подобное использование классом «посторонних» процедур (функций) может лишить класс «объектности». Имеется в виду то, что объект, созданный на основе класса должен быть по возможности самодостаточен – «посторонние» процедуры (функции) лишают класс этого. Кроме того, использование постороннего кода мешает повторному использованию кода, так как придется распространять и подключать к проекту не только модуль класса, но и все модули, использующие друг друга. Следует подробно изучить конструкцию основного цикла таймера. Такое решение применяется часто, и следует уметь им пользоваться. Как видите, запуск цикла происходит в одном месте, а «приказ» остановиться отдается совсем в другом месте путем «пересылки» приказа с «курьером»-флагом. Можно описать эту стратегию и другой фразой: «вставлять палки в колеса». «Палка» здесь – флаг. Кажущееся сложным, это – самое простое решение для такой задачи. Конечно, повторное использование кода, возможное в VBA, не назовешь идеальным. Действительно, это требует подключения к проекту внешнего файла. Но как раз в рамках VBA большего и не требуется. Разочаровывает другое – невозможность создания компонентов-контролов. Вот для этой задачи вам придется использовать VB – или какой-нибудь другой язык. |