Excel vba withevents как использовать
Иногда при разработке надстройки просто необходимо отследить какое-либо событие в книге. Но модуль ЭтаКнига и модули листов надстройки позволяют отследить лишь те события, которые происходят в той книге, в которой этот код прописан. А как же другие книги? Как, например, отследить событие открытия любой книги в Excel и сделать какое-то действие в зависимости от имени открытой книги? Или как отследить выделение ячейки в любой книге? Изменение значений ячеек?
На самом деле все до смешного просто:
В модуле ЭтаКнига главной книги(надстройка либо PERSONAL.XLS) необходимо создать переменную, которая будет ссылкой на все приложение Excel
Private WithEvents App As Application
На событие открытия главной книги (той, в которой пишется код и в которой объявили переменную App - опять же это надстройка либо PERSONAL.XLS) присваиваем этой переменной App значение запущенного приложения Excel:
Private Sub Workbook_Open() Set App = Application End Sub
Т.е. мы теперь имеем как бы свою локальную управляемую ссылку на Excel. Это позволит нам получить доступ к событиям приложения Excel из VBA и отследить их. И среди прочих событий есть такие, которые относятся ко всем открытым книгам. Т.е. то же выделение ячеек мы сможем обработать только внутри своей надстройки, но срабатывать оно будет при выделении ячеек в любой открытой книге.
Теперь создаем непосредственно событие - аналогично выбору других событий в книге в левом окне выбора объектов выбираем App. В правом появятся все доступные события для нашего объекта App:
в этом окне перечислены все события, которые могут быть "перехвачены" в любой открытой книге, а не только той, в которой этот код записан. Сразу после выбора какого-либо события из списка автоматически будет создана пустая процедура, в которую надо будет лишь добавить необходимый код.
Рассмотрим некоторые из этих событий.
Вот так, например, будет выглядеть код отслеживания открытия любой книги :
Private Sub App_WorkbookOpen(ByVal Wb As Workbook) MsgBox "Вы открыли книгу:" & Wb.Name End Sub
Сам по себе код не заработает. Т.к. назначение значения переменной App происходит только при открытии самой книги(надстройки или PERSONAL), то после создания кодов надо будет сохранить эту книгу и открыть заново
А с помощью этого кода можно отследить создание новой книги :
Private Sub App_NewWorkbook(ByVal Wb As Workbook) MsgBox "Вы создали новую книгу" End Sub
Отслеживаем выделение ячеек во всех открытых книгах :
Private Sub App_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range) MsgBox "Вы выделили ячейку с адресом: " & Target.Address End Sub
Target - это объект Range(ячейка или диапазон ячеек), которые были выделены в книге.
Sh - это объект Worksheet, ячейки которого были выделены.
Таким образом у нас есть две переменные, которые мы можем использовать. Например, можно производить определенные действия только на листах с конкретным именем:
Private Sub App_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range) If Sh.Name = "Отчет" Then MsgBox "Вы выделили ячейку с адресом: " & Target.Address End If End Sub
А процедура ниже поможет отследить изменение значений ячеек во всех открытых книгах и отменить нежелательные изменения:
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range) Dim bUndo As Boolean If Sh.Name <> "Для изменений" Then If Sh.Name = "Описание" Then 'для этого листа можно изменять только ячейки диапазона "A16:B20" If Intersect(Target, Sh.Range("A16:B20")) Is Nothing Then MsgBox "На этом листе изменять можно только ячейки в диапазоне 'A16:B20'!", vbCritical, "www.excel-vba.ru" bUndo = True End If Else 'для всех других листов, кроме листа "Для изменений" - изменять значения ячеек вообще нельзя MsgBox "Ячейки на этом листе нельзя изменять!", vbCritical, "www.excel-vba.ru" bUndo = True End If If bUndo Then With Application .EnableEvents = False .Undo .EnableEvents = True End With End If End If End Sub
В приложенном к статье файле будет чуть более понятно что делает эта процедура.
Естественно, в таких процедурах можно назначить выполнение и других(нужных) действий. Например, вызов макроса ( Call ИмяМакроса ). Макрос в таком случае должен быть размещен в стандартном модуле и иметь статус Public (или вовсе без статуса). Сам модуль должен тоже находится в той же книге.
Tips_Macro_How_Catch_Events.xls (60,5 KiB, 4 493 скачиваний)
Это второй из двух разделов, демонстрирующих работу с событиями. В первом разделе, пошаговом руководстве: объявление и вызов событий, показано, как объявлять и вызывать события. В этом разделе используется форма и класс из этого пошагового руководства, чтобы показать, как обрабатывать события при их выполнении.
В Widget примере класса используются традиционные операторы обработки событий. Visual Basic предоставляет другие методы работы с событиями. В качестве упражнения можно изменить этот пример, чтобы использовать AddHandler инструкции и Handles инструкции.
Обработка события PercentDone класса Widget
Поместите следующий код в Form1 :
Ключевое WithEvents слово указывает, что переменная mWidget используется для обработки событий объекта. Тип объекта можно указать, указав имя класса, из которого будет создан объект.
Переменная mWidget объявляется в Form1 том, что WithEvents переменные должны быть уровнями класса. Это верно независимо от типа класса, в который вы размещаете их.
Переменная mblnCancel используется для отмены LongTask метода.
Написание кода для обработки события
Как только вы объявляете переменную с помощью WithEvents , имя переменной появится в левом раскрывающемся списке редактора кода класса. При выборе mWidget Widget события класса отображаются в правом раскрывающемся списке. При выборе события отображается соответствующая процедура события с префиксом mWidget и символом подчеркивания. Все процедуры событий, связанные с переменной WithEvents , присваиваются имени переменной в виде префикса.
Чтобы обработать событие
Выберите mWidget в раскрывающемся списке слева в редакторе кода.
PercentDone Выберите событие из раскрывающегося списка справа. Редактор кода открывает процедуру mWidget_PercentDone события.
Редактор кода полезен, но не требуется для вставки новых обработчиков событий. В этом пошаговом руководстве удобнее просто скопировать обработчики событий непосредственно в код.
Добавьте следующий код в обработчик событий mWidget_PercentDone .
При каждом PercentDone вызове события процедура события отображает процент завершения в элементе Label управления. Метод DoEvents позволяет подписи перекрашивать, а также дает пользователю возможность нажать кнопку "Отмена ".
Добавьте следующий код для обработчика Button2_Click событий:
Если пользователь нажимает кнопку "Отмена" во время LongTask выполнения, Button2_Click событие выполняется сразу после того, как DoEvents инструкция разрешает обработку событий. Переменная mblnCancel уровня класса имеет значение True , а mWidget_PercentDone затем событие проверяет его и задает для аргумента ByRef Cancel значение True .
Подключение переменной WithEvents к объекту
Form1 теперь настраивается для обработки Widget событий объекта. Все, что остается, это найти Widget где-то.
При объявлении переменной WithEvents во время разработки объект не связан с ней. Переменная WithEvents похожа на любую другую объектную переменную. Необходимо создать объект и назначить ему ссылку с помощью переменной WithEvents .
Создание объекта и назначение ссылки на него
Выберите (События формы1) из раскрывающегося списка слева в редакторе кода.
Load Выберите событие из раскрывающегося списка справа. Редактор кода открывает процедуру Form1_Load события.
Чтобы создать Widget процедуру события, Form1_Load добавьте следующий код:
При выполнении этого кода Visual Basic создает Widget объект и соединяет его события с процедурами событий, связанными с mWidget . С этого момента при каждом Widget вызове события PercentDone mWidget_PercentDone выполняется процедура события.
Вызов метода LongTask
Добавьте следующий код в обработчик событий Button1_Click .
Перед вызовом LongTask метода метка, отображающая процент завершения, должна быть инициализирована, а флаг уровня Boolean класса для отмены метода должен иметь значение False .
LongTask вызывается с длительностью задачи 12,2 секунды. Событие PercentDone вызывается каждые одну треть секунды. При каждом вызове mWidget_PercentDone события процедура события выполняется.
Когда LongTask все будет готово, проверяется, mblnCancel закончился ли LongTask он обычно, или если он остановлен, так как mblnCancel задано значение True . Процент завершения обновляется только в первом случае.
Чтобы выполнить программу, выполните следующие действия.
Нажмите клавишу F5, чтобы поместить проект в режим выполнения.
Метод My.Application.DoEvents не обрабатывает события точно так же, как и форма. Например, в этом пошаговом руководстве необходимо дважды нажать кнопку "Отмена ". Чтобы форма обрабатывала события напрямую, можно использовать многопоточность. Дополнительные сведения см. в разделе "Управляемые потоки".
Может оказаться, что программа запускается с помощью F11 и выполняется пошаговое выполнение кода по строке за раз. Вы можете четко увидеть, как выполняется LongTask выполнение, а затем кратко повторно вводится Form1 при каждом вызове PercentDone события.
Что произойдет, если при возврате выполнения в код Form1 LongTask метода был вызван еще раз? В худшем случае переполнение стека может произойти при LongTask каждом вызове события.
Переменная mWidget может обрабатывать события для другого Widget объекта, назначая ссылку новому Widget объекту mWidget . На самом деле вы можете сделать код Button1_Click таким образом каждый раз, когда вы нажимаете кнопку.
Обработка событий для другого мини-приложения
Добавьте следующую строку кода в процедуру Button1_Click непосредственно перед строкой, которая считывает mWidget.LongTask(12.2, 0.33) :
Приведенный выше код создает новый Widget код при каждом нажатии кнопки. Как только LongTask метод завершится, ссылка на Widget нее освобождается и Widget уничтожается.
Переменная WithEvents может содержать только одну ссылку на объект за раз, поэтому при назначении другого Widget объекта mWidget события предыдущего Widget объекта больше не будут обрабатываться. Если mWidget это единственная объектная переменная, содержащая ссылку на старую Widget , объект уничтожается. Если вы хотите обрабатывать события из нескольких Widget объектов, используйте инструкцию AddHandler для обработки событий из каждого объекта отдельно.
Можно объявить любое количество WithEvents переменных, но массивы WithEvents переменных не поддерживаются.
Событие — это действие или ситуация (например, превышение щелчка мыши или предела кредита), которые распознаются некоторым компонентом программы и для которых можно написать код для ответа. Обработчик событий — это код, который вы пишете для реагирования на событие.
обработчик событий в Visual Basic является Sub процедурой. Однако обычно он не вызывается так же, как другие Sub процедуры. Вместо этого вы определяете процедуру как обработчик для события. Это можно сделать с помощью Handles предложения и WithEvents переменной или с помощью Handles . Handles Использование предложения по умолчанию является способом объявления обработчика событий в Visual Basic. Это способ, которым обработчики событий пишутся конструкторами при программировании в интегрированной среде разработки (IDE). AddHandler Оператор подходит для динамического вызова событий во время выполнения.
когда происходит событие, Visual Basic автоматически вызывает процедуру обработчика событий. Любой код, имеющий доступ к событию, может привести к его возникновению, выполнив оператор RaiseEvent.
Можно связать более одного обработчика событий с одним и тем же событием. В некоторых случаях можно отменить связь обработчика с событием. Дополнительные сведения см. в статье Events (Visual Basic) (События в Visual Basic).
Вызовите обработчик событий с помощью Handles и WithEvents
Убедитесь, что событие объявлено с помощью оператора Event.
Объявите переменную объекта на уровне модуля или класса с помощью WithEvents ключевого слова. As Предложение для этой переменной должно указывать класс, который вызывает событие.
В объявлении процедуры обработки Sub событий добавьте Handles предложение, указывающее WithEvents переменную и имя события.
когда происходит событие, Visual Basic автоматически вызывает Sub процедуру. В коде можно использовать RaiseEvent инструкцию для выполнения события.
В следующем примере определяется событие и WithEvents переменная, которая ссылается на класс, который вызывает событие. Процедура обработки Sub событий использует Handles предложение для указания класса и события, которые он обрабатывает.
Вызов обработчика событий с помощью AddHandler
Убедитесь, что событие объявлено с помощью Event оператора.
Выполните оператор AddHandler , чтобы динамически подключить процедуру обработки событий с событием.
когда происходит событие, Visual Basic автоматически вызывает Sub процедуру. В коде можно использовать RaiseEvent инструкцию для выполнения события.
В следующем примере используется оператор AddHandler в конструкторе, чтобы связать процедуру как обработчик событий для FormClosing .
Можно отменить связь между обработчиком событий и событием, выполнив оператор RemoveHandler.
Хотя вы можете визуализировать проект Visual Studio как ряд процедур, которые выполняются последовательно, в действительности большинство программ управляются событиями. Это означает, что поток выполнения определяется внешними вхождениями, называемыми событиями.
Событие — это сигнал, который сообщает приложению, что произошло нечто важное. Например, когда пользователь щелкает элемент управления на форме, эта форма инициирует событие Click и вызывает процедуру обработки события. События позволяют отдельным задачам взаимодействовать друг с другом. Давайте представим, что приложение выполняет задачу по сортировке в отдельном процессе. Если пользователь отменит эту сортировку, приложение отправит событие отмены, по которому процесс сортировки завершит свою работу.
Термины и основные понятия для событий
В этом разделе описываются термины и понятия, используемые с событиями в Visual Basic.
Объявление событий
События объявляются внутри классов, структур, модулей и интерфейсов с помощью ключевого слова Event , как показано в следующем примере:
Вызов событий
События должны создаваться в пределах области действия класса, модуля или структуры, в которых они объявлены. Например, производный класс не может создавать события, унаследованные от базового класса.
Отправители событий
Любой объект, способный создать событие, считается отправителем события или источником события. Например, отправителями событий могут являться формы, элементы управления и пользовательские объекты.
Обработчики событий
Обработчики событий — это процедуры, вызываемые при создании определенного события. В качестве обработчика событий можно использовать любую допустимую подпрограмму с подходящей сигнатурой. Но в качестве обработчика событий нельзя использовать функцию, поскольку она не может возвращать значение источнику события.
Visual Basic использует стандартное соглашение об именовании для обработчиков событий, объединяющее имя отправителя события, символ подчеркивания и имя события. Например, событие Click для кнопки с именем button1 будет называться Sub button1_Click .
Мы рекомендуем придерживаться этого соглашения об именовании при создании обработчиков событий и для пользовательских событий. Но это не обязательное условие, вы можете использовать любое допустимое имя процедуры.
Связывание событий с обработчиками событий
Чтобы обработчик событий мог выполнять свою функцию, его следует связать с соответствующим событием с помощью инструкции Handles или AddHandler .
Оператор WithEvents и предложение Handles
Инструкция WithEvents и предложение Handles предоставляют декларативный способ указания обработчиков событий. Если объект объявлен с ключевым словом WithEvents , созданные им события могут обрабатываться любой процедурой с инструкцией Handles для этого события, как показано в следующем примере:
Инструкция WithEvents и предложение Handles часто являются наилучшим решением для обработки событий, так как декларативный синтаксис позволяет легко создавать, читать и отлаживать код программы. Но использование переменных WithEvents имеет ряд ограничений.
Нельзя использовать переменную WithEvents в качестве переменной объекта. То есть вы не можете объявить ее как Object . При объявлении переменной необходимо указать имя класса.
Так как общие события не привязаны к экземплярам класса, нельзя использовать для WithEvents декларативной обработки общих событий. Аналогично, нельзя использовать WithEvents или Handles для обработки событий от Structure . В обоих случаях для обработки таких событий можно использовать инструкцию AddHandler .
Вы не можете создавать массивы переменных типа WithEvents .
Переменные WithEvents позволяют одному обработчику событий обрабатывать один или несколько видов событий. Также можно создать несколько обработчиков для одного вида событий.
Предложение Handles является стандартным способом связывания события с обработчиком событий, но оно ограничено тем, что может связывать события с обработчиками событий только во время компиляции.
В некоторых случаях, например с событиями, связанными с формами или элементами управления, Visual Basic автоматически заглушает пустой обработчик событий и связывает его с событием. Например, при двойном щелчке кнопки команды в форме в режиме конструктора Visual Basic создает пустой обработчик событий и WithEvents переменную для кнопки команды, как показано в следующем коде:
AddHandler и RemoveHandler
Инструкция AddHandler похожа на предложение Handles , поскольку тоже позволяет задавать обработчик событий. Но AddHandler в сочетании с RemoveHandler обеспечивает большую гибкость, чем предложение Handles . Она позволяет динамически добавлять, удалять и изменять обработчики событий, связанные с событием. Если вы хотите обрабатывать общие события или события от структуры, необходимо использовать AddHandler .
AddHandler принимает два аргумента: имя события, которое предоставляет отправитель события, например элемент управления, и выражение, определяющее делегат. Класс делегата при использовании AddHandler не обязательно указывать явно, поскольку инструкция AddressOf всегда возвращает ссылку на делегат. Следующий пример связывает обработчик событий с событием, создаваемым объектом:
Инструкция RemoveHandler , которая отсоединяет событие от обработчика событий, использует такой же синтаксис, как AddHandler . Например:
Наконец, второй обработчик событий удаляется и событие создается в третий раз. Поскольку теперь нет обработчиков событий, связанных с этим событием, никакие действия не выполняются.
Обработка событий, наследуемых от базового класса
Производные классы, которые наследуют характеристики из базового класса, могут обрабатывать события, создаваемые этим базовым классом, используя инструкцию Handles MyBase .
Обработка событий из базового класса
Объявите обработчик событий в производном классе, добавив инструкцию Handles MyBase. eventname в строку объявления процедуры обработчика событий, где eventname — это имя события в базовом классе, которое вы хотите обрабатывать. Например:
Многие наверняка слышали про модули классов, но не все их используют. На самом деле довольно многие программирующие на VBA за все время программирования прекрасно обходятся без применения модулей классов. Т.к. VBA не является языком объектно-ориентированного программирования(ООП) в строгом смысле слова, то пользовательские классы здесь не обязательны и как следствие не так уж и часто используются при разработке. Это не значит, что VBA не содержит модулей классов: модули книги, листов, пользовательские формы - все это модули классов. Многие, кстати, используют их даже не зная того, что используют именно модули классов. Т.к. модуль листа, книги и формы - это модуль класса, то почти каждый, кто работал с формой работал с модулем класса. В чем их большая польза - с их помощью можно отслеживать различные события объектов. Для форм это события самой формы или любого её элемента - например CommandButton_Click или TextBox_Change. Но мы сейчас рассмотрим лишь тот тип модулей, который в VBA обычно называют модулем класса - Class Module.
Модуль класса(Class Module) – это модуль, содержащий программные коды, которые реализуют работу пользовательских классов. В подавляющем большинстве случаев создается специально для отслеживания событий различных объектов. Создается так же, как и любой другой объект проекта: в окне проводника объектов щелкаем правой кнопкой мыши на нужном проекте-Insert-Class Module
Но прежде чем создать модуль, необходимо понять, что мы будем в нем хранить и для чего он нам. Возьмем для примера самую распространенную проблему: на форме создано несколько ТекстБоксов и необходимо отследить событие ввода данных в эти ТекстБоксы. Обычно делается все просто - для каждого ТекстБокса прописывается отслеживание события:
Private Sub TextBox1_Change() MsgBox "Изменено значение TextBox1" End Sub Private Sub TextBox2_Change() MsgBox "Изменено значение TextBox2" End Sub Private Sub TextBox3_Change() MsgBox "Изменено значение TextBox3" End Sub 'и т.д.
С одной стороны - все верно. А с другой: что если таких текстбоксов у нас не 3, а 43? Не очень удобно для каждого событие прописывать. Да и читабельность такой "портянки" кода тоже значительно падает.
Или другая ситуация - необходимо "на ходу" создать ТекстБоксы на форме и в дальнейшем отслеживать их события. Как тут быть? Ведь раз ТексБоксов еще нет - то и события в форме для них не создать. Создание для них кодов обработки событий заранее ничего не даст - они не будут связаны с самими объектами, поэтому и пытаться даже не стоит. Почему так - при создании элемента вручную VBE делает за нас всю грязную работу - он сам ассоциирует созданный объект с событиями, предназначенные для него заранее. Если же создать объект программно - то часть грязной работы придется делать самим. И создание модуля класса, с описанием в нем объекта ТекстБокс и его событий, как раз очень даже подойдет.
Рассмотрим сразу оба случая. Что нам для этого потребуется:
- для начала создать модуль класса с именем clsmTxtBxes(Insert-Class Module)
- создать стандартный модуль с именем mMain(Insert-Module)
- ну и сама форма тоже не лишняя(Insert-UserForm). У меня форма называется frmTest.
- очень желательно наличие у вас опыта написания хотя бы простейших процедур. Иначе может показаться все очень сложным и непонятным.
Чтобы было проще вникать советую скачать файл с готовыми кодами:
Tips_Macro_UseClassModules.xls (63,5 KiB, 5 598 скачиваний)
Для начала создадим на нашей форме frmTest 4 ТекстБокса, не меняя их имена(по умолчанию они будут TextBox1, TextBox2, TextBox3, TextBox4). Это для того, чтобы понять как применить модули класса к уже созданным ранее на форме элементам.
Далее в стандартный модуль mMain поместим следующий код:
Option Explicit Public aoTxtBxes(1 To 8) As New clsmTxtBxes Sub Show_Form() frmTest.Show End Sub
aoTxtBxes - массив, который будет содержать до 8 ТекстБоксов. Объявляется как Public (чтобы был доступен из любого модуля проекта. Подробнее в статье: Что такое переменная и как правильно её объявить?). Обращаю внимание, что данный массив объявлен как созданный нами модуль класса - As clsmTxtBxes. Это обязательное условие. Если у вас модуль класса называется ClassModule1, то и объявлять aoTxtBxes следует соответственно:
Public aoTxtBxes(1 To 8) As New ClassModule1
но я не приветствую подобный подход, т.к. имя ClassModule1 ни о чем нам не говорит, в то время как clsmTxtBxes сразу дает понять, что там мы обрабатываем ТекстБоксы. Хотя это дело вкуса. Если в одном модуле класса собраны различные событийные процедуры для разных типов( TextBox , ComboBox , ListBox и т.д.) - то конечно, имя лучше дать более общее.
Теперь в созданный модуль класса clsmTxtBxes запишем создание объекта и код, который хотим применить для всех наших ТекстБоксов:
Option Explicit Public WithEvents oTxtBx As MSForms.TextBox 'событие изменения текста в TextBox-ах Private Sub oTxtBx_Change() MsgBox "Вы изменили значение " & oTxtBx.Name, vbInformation, "Информационное окно" End Sub
Завершающий этап - создаем код в модуле формы frmTest , который создаст недостающие ТекстБоксы и свяжет их и ранее созданные с модулем класса:
Option Explicit Private Sub UserForm_Initialize() Dim i As Integer 'Присваиваем последовательно значениям массива aoTxtBxes значения объектов, существующих на форме For i = 1 To 4 Set aoTxtBxes(i).oTxtBx = Me.Controls("TextBox" & i) Next i 'создаем 4 своих TrxtBox-а помимо имеющихся на форме и так же заносим в массив aoTxtBxes For i = 5 To 8 Set aoTxtBxes(i).oTxtBx = Me.Controls.Add("Forms.TextBox.1", "TextBox" & i) 'задаем позицию нового TextBox aoTxtBxes(i).oTxtBx.Left = 100 aoTxtBxes(i).oTxtBx.Top = Me.Controls("TextBox" & i - 4).Top Next i End Sub
Кратко описать, что делает эта процедура, можно так:
Если необходимо больше ТекстБоксов обработать - увеличиваем верхнюю границу массива aoTxtBxes(если хотим вместить 20 текстбоксов - Public aoTxtBxes(1 To 20) As New clsmTxtBxes ). Если заранее неизвестно количество - либо задаем с запасом, либо объявляем aoTxtBxes как динамический массив( Public aoTxtBxes() As New clsmTxtBxes ), а границы определяем в процессе(посредством ReDim Preserve ). Но это уже совершенно другая тема.
Me.Controls.Add("Forms.TextBox.1", "TextBox" & i)
и соответственно изменить/добавить тип переменной в модуле класса:
Tips_Macro_UseClassModules.xls (63,5 KiB, 5 598 скачиваний)
Читайте также: