Функция DoEvents
Уступает выполнение, чтобы операционная система могла обработать другие события.
Синтаксис
DoEvents()
Комментарии
Функция выполнения событий DoEvents возвращает целое число, представляющее количество открытых форм в автономных версиях Visual Basic, таких как профессиональный выпуск Visual Basic. DoEvents возвращает нуль во всех других приложениях.
DoEvents передает управление операционной системе. Управление возвращается, когда операционная система завершает обработку событий в очереди и все ключи в очереди SendKeys отправлены.
Функция DoEvents наиболее полезна для простых действий, таких как разрешение пользователю отменить процесс (например, поиск файла) после его запуска. Для длительных процессов передачу процессора лучше выполнять с помощью функции Timer или передачи задачи компоненту ActiveX EXE. В последнем случае задача может оставаться полностью независимой от приложения, а операционная система заботится о многозадачности и времени нарезки.
Временно уступая процессор в процедуре события, всегда проверяйте, чтобы процедура не выполнялась повторно из другой части вашего кода, прежде чем будет возвращен первый вызов; это может привести к непредсказуемым результатам. Кроме того, не используйте DoEvents , если другие приложения могли взаимодействовать с вашей процедурой непредвиденными способами во время получения контроля.
Пример
В этом примере функция DoEvents используется для вызова передачи выполнения в операционной системе один раз на каждые 1000 итераций цикла. DoEvents возвращает количество открытых форм Visual Basic, но только в том случае, если ведущее приложение — Visual Basic.
См. также
Поддержка и обратная связь
Есть вопросы или отзывы, касающиеся Office VBA или этой статьи? Руководство по другим способам получения поддержки и отправки отзывов см. в статье Поддержка Office VBA и обратная связь.
Функция DoEvents.
Обычно программист ожидает, что Windows (и Visual Basic) постоянно отслеживает все события и реагирует на них. С другой стороны, много времени уходит на различного рода вычисления или сортировку данных. Однако при этом не всегда желательно запрещать приложению Visual Basic реагировать на события. Если создается процедура, в которой много времени будет занято различного рода вычислениями, необходим механизм отслеживания состояния операционной системы и реагирования на различного рода события.
Функция, выполняющая такого рода задачи, носит название DoEvents. В каком бы месте программы ни стоял данный оператор, он сигнализирует Visual Basic о том, что управление передано операционной системе для обработки всех событий. (Windows сохраняет последовательность наступления событий в очереди, а также нажатий клавиш в очереди SendKeys.) DoEvents нельзя использовать в процедуре обработки событий, которая вызывается несколько раз. Например, процедура обработки событий Click может быть вызвана еще раз щелчком мыши. Если забыть про это, можно легко организовать в программе бесконечный цикл.
Цикл, работающий только в случае отсутствия каких-либо событий, носит наименование цикла ожидания. Такие циклы создаются внутри специализированной процедуры Sub, носящей имя Main, и подключающейся к любому программному модулю. В каждом проекте существует только одна процедура Main. Ее формат следующий:
Далее необходимо сделать так, чтобы модуль с Main загружался первым. Для этого нужно выбрать в меню пункт Project|Project Properties и затем страницу General. На этой странице выбрать и открыть список StartUp Object и выбрать Sub Main вместо формы. После того как процедура Main указана загрузочной. Visual Basic не будет автоматически загружать какую-нибудь форму. Для этого теперь необходимо писать программный код, используя, сервированные слова Load и Show.
Ниже приведен простой пример цикла ожидания. Необходимо создать новый проект и добавить к нему программный модуль. Далее в нем объявляется глобальная переменная Counter типа long integer:
Теперь добавим несколько строк кода к процедуре Main:
В завершение установим Sub Main, как загрузочный модуль, и добавим процедуру Form_Click:
После запуска такой программы счетчик будет увеличиваться каждый раз после щелчка внутри формы. Причина состоит в том, что в течение периода ожидания (когда пользователь не щелкает мышью) Visual Basic переходит к процедуре Main и добавляет 1 к счетчику. Поскольку Counter является глобальной переменной, ее значение сохраняется между вызовами процедуры.
Функция DoEvents на самом деле возвращает число форм, загруженных приложением в текущий момент. Цикл ожидания останавливается, когда выгружены все формы. (Или Visual Basic встречает оператор End.)
Другое общее использование функции DoEvents внутри функции состоит в запуске больших по времени вычислений. Установка цикла ожидания позволяет Visual Basic реагировать на события в момент вычислений. Небольшое дополнительное время при этом с лихвой окупается.
Doevents vba excel как использовать
Для того, чтобы твое приложение, имеющее длинные циклы, не мешало пользователю на время их выполнения заняться другой работой, временно передвинув или даже свернув твою красивую форму, в нашем могучем арсенале имеется невостребованная пока функция DoEvents, синтаксис которой приятно ласкает взгляд:
DoEvents()
и никаких тебе аргументов.
Эта функция, согласно мануалу, возвращает значение типа Integer, представляющее собой количество открытых форм, если фокус находится на форме Visual Basic и ноль, если фокус находится на окнах других приложений. Ну это кому что интересно. Главное это то, что эта функция передает управление операционной системе, то бишь Windows, для обработки других событий. И после обработки Windows’ом всех событий из очереди и передачи всех нажатий клавиш из очереди функции SendKeys возвращает управление обратно в Visual Basic.
Хотя в использовании этой функции нет особенных трудностей, учитывая специфику сайта, лучше один раз пощупать функцию в маленькой процедурке, чем искать ошибку в большом проекте.
Создадим Exe-проектик, положим на форму кнопку Command1 и текстбокс Text1и напишем маленькую процедурку:
Private Sub Command1_Click()
Dim x As Long
For x = 1 To 300000
Text1 = x
Next x
End Sub
Этот цикл по нашему разумению должен выводить в Text1 постоянно меняющееся значение переменной X от 1 до 300000. Запустим проект, нажмем кнопку Command1 и вместо созерцания мигающих циферек в текстбоксе мы видим. ничего мы не видим — форма наша не обновляется до окончания цикла. Да мы еще и не можем корректно приостановит выполнение цикла (я имею в виду , если он откомпилирован). Более того, попробовав передвинуть мышью форму мы убедимся, что это невозможно, а Менеджер задач (Task Manager) может выдать сообщение, что программа зависла и не отвечает (not responding). Вот так, ходим по минам, а подрываемся на говне.
Однако всего этого можно избежать, если ввести в цикл функцию DoEvents. Тогда наша процедурка может выглядеть так:
Private Sub Command1_Click()
Dim x As Long
For x = 1 To 300000
Text1 = x
DoEvents
Next x
End Sub
Конечно мигание цифр не радует глаз. Кроме того, при передаче управления Windows выполнение программы Visual Basic (в нашем случае цикла) приостанавливается, но зато все наши проблемы мгновенно решились: и форму можно двигать и форма обновляется.
В связи с тем, что повторный вызов функции DoEvents в момент, когда функция еще не передала управление из операционной системы назад в Visual Basic может вызвать непредвиденные последствия, нежелательно вызывать процедуру DoEvents из других процедур программы. Видимо эти же соображения имеют место, когда предполагается, что нежелательно использовать функцию DoEvents в процедурах обработки событий Click. Но мне не удалось добиться никаких особенных фатальных результатов, кроме перезапуска цикла, даже беспрестанно кликая по кнопке Command1.
Основным недостатком этой функции является очень значительное уменьшение быстродействия в длинных по времени циклах (в коротких ее вообще нет смысла использовать). Самое простое решение этого вопроса — запуск DoEvents не при каждой итерации, а например, при каждом сотом (или тысячном) проходе цикла, в зависимости от его параметров. Решить это можно, например, вводя дополнительную переменную-счетчик (в нашем примере y). С каждым проходом цикла счетчик увеличивается на единицу, а при достижении им значения, например 1000, вызывает функцию DoEvents и обнуляется, чтобы при дальнейшей работе цикла снова увеличится до 1000. Тогда наша процедура будет выглядеть следующим образом:
Private Sub Command1_Click()
Dim x As Long
Dim y As Long
For x = 1 To 300000
Text1 = x
y = y + 1
If y = 1000 Then ‘проверяем счетчик итераций
‘и если он совпадает с заданым числом, запускаем DoEvents и обнуляем счетчик
DoEvents
y = 0
End If
Next x
End Sub
Если тебя уже раздражает такое "ламерское" (но на мой взгляд удобное и наглядное решение), можно отследить тысячный проход цикла по-другому, например деля значения счетчика цикла x на эту тысячу с остатком (Mod) при каждой итерации, тогда при остатке ноль можно считать, что значение х кратно нашей тысяче. Тогда наш код будет выглядеть так:
Private Sub Command1_Click()
Dim x As Long
Dim y As Long
For x = 1 To 300000
Text1 = x
y = y + 1
If x Mod 1000 = 0 Then DoEvents ‘получаем остаток от деления
‘и если он равен нулю, запускаем DoEvents
Next x
End Sub
А может быть снижение быстродействия не так уж и значительно и им можно пренебречь? Чтобы нам не принимать все на веру, мы сами проведем соответствующее сравнительное исследование зависимости быстродействия от частоты срабатывания функции DoEvents. Для этого нам на форме дополнительно понадобятся Timer1, и еще два текстбокса Text2 и Text3. В уже имеющийся Text1 мы будем вводить интервал цикла (по умолчанию он у нас будет 300000), в Text2 — на какой итерации (1-ой, т.е. каждой, 100-ой, 10000-й и т.д ) будет включаться функция DoEvents. В Text3 выведем результат измерений в миллисекундах.
Объявим три переменных:
Option Explicit
Dim Iter As Long ‘итерация срабатывания DoEvents
Dim RunTime As Integer ‘время работы цикла
Dim RunLong As Long ‘длина цикла
В процедуре Form_Load присвоим начальные значения цикла и итераций:
Private Sub Form_Load()
Text2 = 1
Text1 = 1000000
End Sub
Код процедуры Command1_Click Изменим следующим образом:
Private Sub Command1_Click()
Dim x As Long
Dim y As Long
Iter = Val(Text2) ‘Присваиваем переменным значения для цикла
RunLong = Val(Text1)
RunTime = 0
Text3 = "Цикл в работе"
Timer1.Interval = 1 ‘устанавливаем интервал таймера в 1 мск
Timer1.Enabled = True ‘запускаем таймер
For x = 1 To RunLong
‘обратите внимание на то, что вывод значений переменной цикла X не выводится в Text1,
‘так как это непомерные затраты времени и исказят наши значения.
‘Text1 = x
y = y + 1
If y = Iter Then
DoEvents
y = 0
End If
Next x
Timer1.Enabled = False ‘выключаем таймер
Text3 = RunTime ‘выводим значение счетчика таймера в текстбокс
End Sub
‘В процедуре таймера мы просто имеем счетчик, который увеличивается
‘ на единицу при каждом включении, т. е. каждую миллисекунду.
Private Sub Timer1_Timer()
RunTime = RunTime + 1
End Sub
Скажу сразу, что данный эксперимент не ставит собой целью получить точные данные, т. к. это невозможно хотя бы из-за того, что процессор в Windows’е постоянно выполняет определенную работу в фоновом режиме, и хотя мы этого не видим, но отнимает время у нашей программы. Для получения корректных результатов промежуток времени работы цикла должен быть большой, с тем, чтобы погрешность, скажем 5 мск была бы не критичной.
Попробуй запустить тест. При этом, конечно, не надо двигать форму по экрану, чтобы не занимать дополнительного времени. Мы увидим, что при включении функции DoEvents каждую 10-ю итерацию ускоряет цикл в 7-8 раз, каждую 100-ю — в 18-20 раз. Т.е. функция DoEvents снижает быстродействие просто в разы. Но тем не менее, использовать надо, но очень разумно.
Используя эту функцию мы можем позволить пользователю прервать продолжительный процесс. Для этого достаточно ввести булеву переменную для флага, например Flag , которая и будет сигнализировать о том, что процесс необходимо прервать.
Добавим на форму кнопку Command2, которой и будем прерывать выполнение цикла. В секцию (General) добавим объявление флага
Dim Flag As Boolean
В процедуре Command1_Click, прямо в самом начале установим начальное для цикла значения флага:
Flag = False
а в самом цикле поставим условие на выход
If Flag = True Then Exit Sub
Теперь, для выхода из цикла надо просто изменить значение флага False на True. Это мы и сделаем в процедуре Command2_Click:
Private Sub Command2_Click()
Flag = True
Text3 = "Цикл прерван"
End Sub
Вот и готово. Теперь наша кнопка Command2 прерывает цикл. Скачать исходник примера можно вверху страницы.
С помощью функции DoEvents можно также организовать цикл ожидания, прерываемый пользователем. В качестве примера сделаем программку-шутку, которая иммитирует предупреждение пользователю о начале форматирования жесткого диска. Для этого в новом Exe-проекте на форме нам понадобится Label1, Command1 и Timer1.
Объявим пару переменных:
Option Explicit
Dim RunTime As Integer ‘время в секундах
Dim Flag As Boolean ‘флаг, сигнализирующей о прерывании цикла
Для процедуры, собственно, цикла ожидания я выбрал событие формы _Activate, так как оно возникает, когда форма уже загружена и инициализирована и элементы на ней видимы.
Private Sub Form_Activate()
RunTime = 10 ‘устанавливаем начальные значения секунд
Timer1.Interval = 1000 ‘интервал срабатывания таймера 1 сек
Timer1.Enabled = True ‘ включаем таймер
‘организуем цикл, работающий до тех пор, пока переменная RunTime
‘не стане равной нулю (изменяется таймером)
Do Until RunTime = 0
DoEvents ‘постоянно передаем управление Windows
If Flag Then ‘проверяется, нажата ли кнопка Command1 для
‘прерывания цикла (Flag=True) или нет (Flag=False)
Label1 = "Молодец, успел, но все равно форматирование начинается!"
Exit Sub ‘выход из процедуры, если кнопка Command1 нажималась
End If ‘конец условия
Loop ‘конец цикла
Timer1.Enabled = False ‘выключение таймера
Label1 = "Не успел, не успел, надеюсь на винчестере ничего ценного нет?"
End Sub
Private Sub Timer1_Timer()
RunTime = RunTime — 1
‘и выводим соответствующее сообщение в лейбл
Label1 = "Форматирование диска начнется через " & RunTime & " сек. " _
& " Успеешь нажать кнопку?"
End Sub
Процедура Command1_Click() управляет флагом и выключает таймер
Private Sub Command1_Click()
Timer1.Enabled = False
Flag = True
End Sub
Скачать исходник этого примера можно отсюда.