- Remove From My Forums
Need help on Excel VBA Application WithEvents — contorlling BeforeClose and BeforeSave
-
Question
-
Hi,
I’m working with application events and want to control the following workflow:
1. When application starts, it creates xlApp WithEvents (in a class module)
Private WithEvents xlApp As Excel.Application
2. I want to stop the standard ‘SaveAs’ dialog showing but instead have created a Userform (as my new SaveAs). This is to be triggered with either the
xlApp_WorkbookBeforeClose or
xlApp_WorkbookBeforeSave procedures.3. When calling it via the BeforeClose event and which triggers the Save event, I have chosen to set
Cancel=True which stops the standard dialog and then I call my Userform — All OK so far.4. But when I decide to click Cancel via the Userform (where I would like to close the workbook as unsaved), I’m still being prompted for the standard «Save, Don’t Save and Cancel» prompt as it attempts to close the workbook again.
How do I stop this from showing again as the workbooks tries to close?
I added boolean flags to test and trap for this in order to force a close method using
wb.Close False but this crashes the whole application.The Cancel argument via the xlApp_WorkbookBeforeClose procedure is of no use as this just stops the close event being triggered altogether.
Some help would be ap[preciated please.
Answers
-
Did you add any code to the close event?
Private Sub xlApp_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean) If MsgBox("Actual Do save", vbYesNo) = vbYes Then Application.EnableEvents = False Wb.SaveAs "C:temptest.xlsx" Application.EnableEvents = True Else Wb.Saved = True End If End Sub
Cheers,
Andy
www.andypope.info-
Proposed as answer by
Wednesday, December 7, 2011 2:52 AM
-
Marked as answer by
danishani
Thursday, January 12, 2012 12:49 AM
-
Proposed as answer by
Events And Event Procedures In VBA
This page describes Events and Event Procedures in VB and/or VBA.
Event programming is a very powerful tool that you can use within your VBA code to monitor user actions, take appropriate action
when a user does something, or monitor the state of the application as it changes. If you are programming with your own custom classes, you
can extend the functionality of these classes by defining and raising your own custom events, broadcasting the event message to any
object that is listening for events from your class.
Events and Event Procedures were introduced in Excel97. Earlier versions of Excel do not support events. Events and event
procedures are the same for versions 97 through 2007. No significant changes have been made to the event model since its
introduction in Excel97. A few new events have been added, but the overall structure of the event system is unchanged.
An Event is an action initiated either by user action or by other VBA code. An Event Procedure
is a Sub procedure that you write, according to the specification of the event, that is called
automatically by Excel when an event occurs. For example, a Worksheet object has an event named
Change. If you have properly programmed the event procedure for the Change
event, Excel will automatically call that procedure, always named Worksheet_Change and always in the code module of the worksheet,
whenever the value of any cell on the worksheet is changed by user input or by other VBA code (but not if the change in value is
a result of a formula calculation). You can write code in the Worksheet_Change event procedure to take
some action depending on which cell was changed or based upon the newly changed value. (The
Worksheet_Change event might more properly be called Worksheet_AfterChange
since it is called after the cell(s) has been changed. There is no way to access the previous value of the cell before it was changed.)
For the Change event, the system will pass you a Range reference named Target that refers to the
cell(s) being changed. You can examine that variable to determine if your code needs to carry out some action or whether it can ignore
the change and get out with an Exit Sub statement. See the Sample Event Procedure section below.
The events and their procedure declarations are fixed. You must not alter the name or parameter list of an event procedure. The VBA
Editor will automatically insert the correct procedure declaration. Do not alter this. You cannot create new events for an Excel
object. The events are «hard coded» into the object, such as a Worksheet, and may not be changed. (You can, however, create custom
events for your own classes. See the Creating Your Own Events In Your Class Modules section later in this article.
There are many events that are defined in a Worksheet object, the Workbook object, and the Excel Application object itself.
On this page, we will cover Application events in only in general terms, but not in much detail since they require a different
coding model. (See Application Events for a discussion of Application events.) An event is said to be
raised when the action that initiates the event occurs and the application automatically sends a signal to all
components connected to event-generating object (e.g, a Worksheet) indicating that the event has occured. An Event Procedure
is a VBA Sub procedure that is executed automatically by Excel when the event is raised. It is important to remember that
in addition to user input, events may run as the results of actions taken by other VBA code. There is no direct way to determine whether
the cause of the event was user input or VBA code. You should write your events procedures such that this distinction does not matter.
Since VBA can cause an event procedure to run, it is possible that your code may end up in a loop. See Preventing Event Loops
later in this article for information about event loops and how to avoid them with proper coding.
For the Change event in particular, it should be noted that this is triggered when a cell
is changed by user action or by other VBA code, but is not raised if the value of a cell is changed as a result of formula calculation.
EVENTS — On this page, we will be discussing only Excel’s events related to Sheets, Workbooks, and the Application,
which are completely independent of the events for user forms and controls on user forms. The EnableEvents
setting, discussed later, has no effect on events of user forms or controls on user forms. For information about supressing events
for controls on a form, see Suppressing Events In UserForms. Events on Charts are a special
case of events and need special code handling.
OBJECT MODULES — Everything related to event handling — the definition of event procedures, creating a
WithEvents variable to receive events, and creating your own events — takes place
within Object Modules. Besides setting the EnableEvents property, there is
nothing related to events in a standard code module; every thing is in an object module. An Object Module is
any one of the following:
A Class module.
A Sheet module (either a worksheet or a chart sheet).
The ThisWorkbook module
The code module behind a User Form
You can use the Object Browser in the VBA Editor to determine what events are avaiable for the three objects that generate events — the Worksheet,
the Workbook, and the Application. (See the notes on ChartObjects and Charts in the blue box in the Event Hierarchy section below.)
Open the Object Browser in the VBA Editor (press F2 or choose Object Browser from the View menu.
In the Classes list, scroll down and select Worksheet. Right-click anywhere in the primary window and choose Group Members on the pop up menu.
Then scroll down in the Members Of «Worksheet» list until you see items with yellow lightening bolts next to them.
These are the events for the Worksheet objects. Do the same for the Workbook and
Application objects. For help on a particular object or event, select it in the Object Browser and
press F1 for Help on that topic (note that not all events are documented — you may have to access event information by going through
the object to which the event belongs.
Another method to determine what events are available is to create an empty class module, and enter the following code:
Dim WithEvents App As Application
Dim WithEvents WB As Workbook
Dim WithEvents WS As Worksheet
Dim WithEvents CHT as Chart
Then, select each of App, WB, WS, and CHT
elements in the left side dropdown at the top of the code window. All the events for the selected item in the left dropdown will be listed in the
right dropdown. If you see an item of interest, let go of the mouse button and the VBA editor will insert that event’s
procedure declaration in the code module. The declaration will tell you what the parameters for the event are, but you will still need
to use Help to obtain a description of the meaning and usage of the parameters. Event procedures must be declared exactly as they are defined.
This is why it is good practice to let the VBA Editor insert your procedure shell. Do not change any of the VBA generated code.
The easiest way to start with events and event procedures is to allow the VBA editor to build the shell code for you. In Excel,
right click one of the sheet tabs at the bottom of the main window and choose View Code from the pop-up menu. This will
open the VBA Editor to the code module associated with that worksheet. In that code window you will see two dropdown boxes at
the top of the code window, as shown below:
Change the (General) setting to Worksheet and then change SeletionChange to Change. This will add
the event procedure declaration for the Change event to the code module, as shown below:
Within the Worksheet_Change procedure, you can add any code that you want to take place when a cell value
is changed. This event is raised automatically by Excel when a cell’s value is changed either by user input or by other VBA code. It
is not raised if the value of the cell is changed by a formula calculation in that cell. The Target parameter
is a Range type object referring to the cell(s) that were changed. To use other events of the worksheet, select the event in the right hand
dropdown at the top of the code pane. When you change this setting, VBA will insert the procedure shell in the code module for you,
ensuring that the procedure is properly declared. Under no circumstances should you change anything in the Sub
statement created by VBA. Doing so may cause the event not to work.
For sheet (both worksheet and chart sheet) level events, the event procedure code must be placed in the Sheet module associated with that sheet.
Workbook level events must be placed in the ThisWorkbook code module. If an event procedure is not in the proper module, VBA will
not be able to find it and the event code will not be executed. It is generally accepted good programming practice that only event
procedures be included in the sheet modules and the ThisWorkbook modules. If you have no compelling reason to put other code in the
sheet or ThisWorkbook modules (and there are a few quite legitimate reasons to do so, but they are beyond the scope of this article)
you should put it in a standard code module.
There is no built in object to catch Application events. You can do either of two things, as described
below, in the Application Events section of this page: use the ThisWorkbook code module or use a dedicated class module.
Placing the code in the ThisWorkbook module requires slightly less code, but I prefer to use a dedicated class module for
organizational purposes — one module for each logical function group. Neither method is particularly better than the other.
Use the method that is easiest for you.
As noted above, events are generated by:
- The Application
- The Workbook
- The Worksheets
- Charts
If a Chart is a Chart Sheet, then it follows the rules of a Worksheet, except that its events are no replicated by the Workbook
or Application objects. If a Chart is part of a ChartObject embedded on a worksheet, it follows its own rules. See
the Charts And ChartObjects subsection, in blue, later in the article.
An object contains events for itself as well as replications of events for its subordinate objects. Since the Worksheet
is at the bottom of the hierarchy and has no subordinate objects (at least no objects that have events, that have events), so the Worksheet contains
only events for itself. For example, each worksheet has an event named Worksheet_Change that is triggered when
a cell on that worksheet is changed either by user input or by VBA (but not if the change is the result of a calculation). Each worksheet’s
Worksheet_Change event is exclusive to that sheet.
SUBORDINATE AND SUPERIOR OBJECTS — In the article, we will use the term Subordinate object to refer to an object below some other
object in the hierarchy of event-generating objects. The term Superior object refers to an object that is higher up in the
hierarchy of event-generating objects. For example, Worksheet is a subordinate object, to both the
Workbook and Application object. The Workbook
is both a subordinate and superior object; it is a superior object to the Worksheet object and is
a subordinate object to the Application object. Though Charts (either Chart Sheets or Chart objects
in ChartObject objects on a worksheet) do raise events, they don’t fit into the hierarchy. As far as the event generation object
model is concerned, Charts are orphans. See the CHARTS AND CHARTOBJECTS notes later in this section.
The Workbook object is higher up in the hierarchy. Worksheets are subordinate to the workbook. Therefore, the
Workbook object has events for itself, such as BeforeSave as well has versions of all
the events of the Worksheet class. For example, every worksheet has a Worksheet_Change event
that is called in response to changes on that worksheet. The Workbook object also has a
Workbook_SheetChange event that is called when any cell on any worksheet is changed. When a cell value is changed,
both the worksheet’s Worksheet_Change and the workbook’s Workbook_SheetChange events
are triggered, and a reference to the changed cell(s) is passed to event procedure.
Since the Application object sits at the top of the hierarchy, it contains events for itself, such as App_NewWorkbook
as well as events for all Workbook events and all Worksheet events. Since every event «rolls up» to the Application
object, it would be possible to write all the event code within the structure of Application Events. However, this would be very cumbersome
and would not take advantage of the modularization that separates event drivers (Application, Workbook, and Worksheet) provide. The code would
get very complicated very quickly.
CHARTS AND CHARTOBJECTS — Charts do have events (although ChartObjects on a worksheet do not), but they do not fit nicely into the regular hierarchy
of the event-generating objects. If you have a Chart Sheet (as oppsosed to a Chart in a ChartObject residing on a Worksheet),
the chart sheet acts much the same way as a worksheet with respect to events, albeit with a different set of events. Moreover,
these events do not have counterparts in the Workbook object or the Application object.
Charts are kind of orphans in the grand scheme of events. A Chart object that is part of a ChartObject on a worksheet also has events,
but like the Application object, there is no ready-made container for events of Charts that are part of a ChartObject
on a sheet. Instead, you must use either of the techniques described later for the Application object
— just substitute «As Chart» for «As Application» and set the event class variable to
Sheet1.ChartObjects(«MyChart»).Chart. ChartObjects do not have events — it is the Chart object within the
ChartObject object that has the events. Events for Charts, either Chart Sheets or Charts in embedded ChartObject do not have their events
replicated in either the Workbook or the Application objects. Charts are sort of the «oddball» object of Excel’s event system.
The following code may be placed in the ThisWorkbook object module to access events of a
Chart in an embedded ChartObject object.
Public WithEvents CHT As Chart Private Sub Workbook_Open() Set CHT = Worksheets(1).ChartObjects(1).Chart End Sub Private Sub CHT_Activate() MsgBox "CHT: TypeName: " & TypeName(CHT) & vbCrLf & _ "CHT Name: '" & CHT.Name & "'" & vbCrLf & _ "CHT Parent TypeName: " & TypeName(CHT.Parent) & vbCrLf & _ "CHT Parent Name: " & CHT.Parent.Name End Sub
If you have event code in the sheet, the workbook, and the application classes, the event will be raised in all three of these objects. Even if a change
is trapped by a sheet level Worksheet_Change event, the event procedure in the Workbook and the Application will also be
raised. The order of events is from the least significant object (the Sheet) upwards through the most significant object (the Application).
You can stop the event from being triggered «upstream» (e.g., preventing the Workbook_SheetChange and the
App_SheetChange event from being raised) by setting the Application.EnableEvents
property to False. For example, in a sheet’s code module:
Private Sub Worksheet_Change(ByVal Target As Range) Application.EnableEvents = False Application.EnableEvents = True End Sub
This code processes the cell change event at the Sheet level, but the line
Application.EnableEvents = False prevents the Worksheet and Applicaton
SheetChange events from being raised. Indeed, this line of code suppresses all events
from being raised until its value is reset to True. Note that Excel never automatically sets
Application.EnableEvents back to True
(as it does do with the ScreenUpdating property). It is up to your code, including well designed
error handling code, to ensure that Application.EnableEvents is properly reset to True.
See Error Handling In VBA for more information about error handling code.
This section will examine a very simple event, the Worksheet_Change event, and illustrate
a few useful techniques you can use to determine whether your code needs to act in response to the event. The basic event code, as
generated by VBA is as follows:
Private Sub Worksheet_Change(ByVal Target As Range) End Sub
As written, the event procedure does nothing — there is no code within the procedure. The Target
parameter is a Range object that refers to the cell(s) that were changed. Since Worksheet_Change runs for
every cell in the worksheet, you most likely will need to test whether Target is within some predefined
range of interest. If it is that range, you’ll do something. If Target is not in that range, you don’t
want to carry out the action of the procedure. The easiest way to do this is with the Intersect method.
Intersect returns a range of cells that are included in two ranges. For example, the Intersection of the range
A1:C3 and C3:F6 is the cell C3 since that cell
is common to both ranges. If there are no cells in common between two ranges, Intersect returns
Nothing. Thus, you can use intersect to see if Target is within the range
of interest:
If Not Application.Intersect(Target, Me.Range("A1:C10")) Is Nothing Then Else Exit Sub
You could also use named ranges rather than hard coded cell references. This is the preferred approach.
There may be times you want to act only if a single cell was changed, and ignore it if multiple cells are changed. In that case,
you can use
If Target.Cells.Count > 1 Then Exit Sub
Here, if Target contains more than one cell, get out of the procedure. In a similar fashion you can test whether
Target is within a specified column or row or range of columns and rows. Any of the following code should get you
started:
If Target.Cells.Count > 1 Then Exit Sub End If If Target.Columns >= 3 And Target.Columns <= 10 Then Else Exit Sub End If If Target.Row >= 5 And Target.Row <= 10 Then Else Exit Sub End If
Since Target is a Range object, you can perform any number of tests using the vast flexibility of a Range
object to determine whether your code should act on the change or simply ignore it by calling Exit Sub.
It is possible that you need to respond to the events of a specific worksheet differently than you would for other worksheets, and that
the name of the special worksheet is not known until run time (e.g., it might be a sheet added by your application). You could handle
this in the Workbook_SheetChange event, but it would require cumbersome logic to process events for
only one worksheet rather than all worksheets. A better and more elegant solution is to create a special class module and within that
module declare a variable of type Worksheet using the WithEvents keyword. For example,
suppose you want to handle events for a worksheet that is created at run time. In a standard code module, declare a
Collection object as:
Public WSColl As Collection
Next, create a class named CWorksheetObject and insert the following code:
Public WithEvents WS As Worksheet
Private Sub WS_Change(ByVal Target As Range) Debug.Print "Special Code For New Worksheet" End Sub
This code declares a variable named WS of type Worksheet using the
WithEvents keyword. WithEvents connects the event system to the
WS variable and lets you utilize the events of the object. Next, you would include the
event procedures for this worksheet:
Private Sub WS_Change(ByVal Target As Range)
Debug.Print "Special Code For New Worksheet"
End Sub
Finally, you create an instance of the CWorksheetObject class and set its WS
variable to the newly created worksheet. Once the WS variable is set to a specific worksheet, the
event procedures in that class will run for events on the assigned worksheet.
Sub TestProc() Dim WSObj As CWorksheetObject Dim WSheet As Worksheet If WSColl Is Nothing Then Set WSColl = New Collection End If Set WSObj = New CWorksheetObject Set WSheet = Worksheets.Add() WSheet.Name = "Some Name" Set WSObj.WS = WSheet WSColl.Add Item:=WSObj, key:=WSheet.Name End Sub
The TestProc procedure first declares a variable named WSObj of type
CWorksheetObject. At this point, the object exists, but its WS Worksheet
object has not yet been set to any specific workbook, so no events will fire in the class. The code then creates a new worksheet,
names that worksheet, and then sets the WSObj‘s WS object to the newly
created worksheet. Now that the WS object has been set to a specific worksheet, it will respond to
events generated by the newly created worksheet. Finally, it stores the WSObj variable in the
WSColl Collection variable so the object is not destroyed when it goes out of scope at the end
of the procedure.
Using the method above, and expanding on it to use other object types, you can simplify programming tasks that might otherwise
require much more complicated logic.
There are two common ways to declare application events (though because VBA is as versatile as it is, there are many other
ways to implement Application events). The first is to declare the App variable (of type Application in
the ThisWorkbook code module. The second method is to use a dedicated code module.
In the ThisWorkbook code module, insert the following code:
Public WithEvents App As Application Private Sub Workbook_Open() Set App = Application End Sub
Then, select App in the left side dropdown at the top of the ThisWorkbook code pane
and choose in the right side dropdown which of the available events you wish to use. VBA will automatically insert the
proper declarations for that event. Remember, never change the code that VBA inserts for you. If you do change it, it
is quite likely that the code will not work properly, if at all.
You can then use events for the App object such as:
Private Sub App_NewWorkbook(ByVal Wb As Workbook) MsgBox "New Workbook: " & Wb.Name End Sub
The second approach to creating Application Events is to use a dedicated class module. Insert a class module into your project and
name the class module CExcelEvents. In that class module, enter the following code:
Private WithEvents XLApp As Application Private Sub Class_Initialize() Set XLApp = Application End Sub
Then, change the left side dropdown at the top of the code pane to XLApp and choose an event from the right side dropdown. VBA
will automatically insert the proper procedure shell for that event. As before, do not change the code generated by VBA.
You can then define your application event procedures in the class module. For example,
Private Sub XLApp_NewWorkbook(ByVal Wb As Workbook) MsgBox "NewWorkbook" & Wb.Name End Sub
The next step is to create a variable of type CExcelEvents and initialize that variable to a new instance
of CExcelEvents. In the ThisWorkbok code module, declare a variable as shown below:
Private ExcelEvents As CExcelEvents Private Sub Workbook_Open() Set ExcelEvents = New CExcelEvents End Sub
Since the Class_Initialize procedure of the CExcelEvents class initializes the
XLApp variable when the class is created, we do not have to worry about initializing XLApp.
Any Application event procedures should be added to the CExcelEvents class.
Given that there are at least two method for creating an object to receive Application Events, you may be wondering which is better,
a separate class module or the ThisWorkbook module, Neither is better in any significant way. As a matter
of personal preference and coding style, I put my application events in a dedicated class module. In my opinion, this keeps to
project better organized. However, beyond that, there is no advantage to use a dedicated class module for Application events. You should
use the approach that seems most natural to your own coding style. Once you decide on a method, stick with that method across projects.
Don’t mix and match.
Without proper coding, your event procedures can end up in infinite
recursive loops. Depending on your version of VBA and Excel, this may result in
an non-trappable Out Of Stack Space error or VBA will simply terminate execution when some threshold (approximately 300) number
of calls is met. Consider, for example, the following code:
Private Sub Worksheet_Change(ByVal Target As Range) Target.Value = Target.Value + 1 End Sub
At first glance, this code may seem perfectly valid. When a cell is changed to some value by the user, the code adds one the that value,
so if a user enters 1, the code will change that to 2. However, this is not what will actually happen. When the user changes the cell to
1, the event procedure runs and changes the value to 2. This change, however, raises the Change event again
and the code will run to change the 2 to a 3. This again raises the Change event, which changes the value 3 to 4.
Yet again, the Change event runs, changing the 4 to a 5. This looping will continue until VBA aborts
the loop or you run out of stack space.
In order to prevent this runaway looping, you can use the EnableEvents property of the
Application object. When you set this property to False VBA will not raise any
events, and the example Change event will run once only for the input by the user. It will not run when
the value is changed by the VBA code. You should always be sure to set EnableEvents property back
to True to enable events to be called normally. Unlike some properties (such as ScreenUpdating),
Excel will not automatically change EnableEvents back to True. Your code must ensure that the value
is properly reset. For example, in the code that follows, the Target value is incremented once, but
since EnableEvents value is False, no subsequent Change
event is raised.
Private Sub Worksheet_Change(ByVal Target As Range) Application.EnableEvents = False Target.Value = Target.Value + 1 Application.EnableEvents = True End Sub
In some circumstances, it may not be desirable to disable all event handling using Application.EnableEvents = False.
Your application may rely on various events running when they should. You can work around this by creating a public Boolean variable, testing
that variables in your event procedure, and exiting the procedure if that variable is True. This way, you can turn off one event handler while
leaving the other event handling in place. For example, in a standard code module, declare a variable such as:
Public AbortChangeEvent As Boolean
Then, in the Worksheet_Change event procedure, you test this variable. If it is true, you would
immediately exit the procedure, as shown in the example below.
Private Sub Worksheet_Change(ByVal Target As Range) If AbortChangeEvent = True Then Exit Sub End If End Sub
Finally, you would disable the Worksheet_Change event by setting the AbortChangeEvent
variable to True. For example,
AbortChangeEvent = True Range("A1").Value = 1234 AbortChangeEvent = False
The code above disables only the Worksheet_Change event and only for the one line code. In general,
using Application.EnableEvents = False is sufficient, but there may be circumstances in which more
complex event handling is necessary.
Because the event object model includes implementations of the events of subordinate objects (e.g.,
Application has events for the Workbook and the
Worksheet objects), you may find that some results are different than what you may expect.
EVENT ORDER: It is important to note that the event procedures of a subordinate object (e.g., the Worksheet
will run to completion before an event of a superior object (e.g., Workbook) is called. That is, the Worksheet
event procedure Worksheet_Change will run to conclusion, to the End Sub statement,
before the Workbook event procedure Workbook_SheetChange occurs. Thus, you cannot assume that the Workbook and/or
Application SheetChange have been executed within your code in the Worksheet_Change event
procedure. You should assume the opposite — the events of the superior object will not yet have run.
For example, create a class named CExcelEvents and insert the following code:
Public WithEvents App As Application Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range) Counter=Counter + 1 Debug.Print "Counter: " & CStr(Counter) End Sub
Then, put the following code in the ThisWorkbook module:
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range) Counter = Counter + 1 Debug.Print "Counter: " & CStr(Counter) End Sub
Next, put the following code in the code module for Sheet1:
Private Sub Worksheet_Change(ByVal Target As Range) Counter = Counter + 1 Debug.Print "Counter: ", CStr(Counter) End Sub
Finally, put the following code in a standard code module like Module1:
Public Counter As Long Public XLApp As CExcelEvents Sub AAA() Set XLApp = New CExcelEvents Counter = 0 End Sub Sub BBB() Debug.Print "*** COUNTER: ", CStr(Counter) End Sub
Now run the procedure AAA to get things set up. Next, type something into any cell on Sheet1. Finally
run the procedure BBB. Open the Immediate Window if it is not already open (CTRL G)
and look at the messages. You should see:
Counter: 1 Counter: 2 Counter: 3 *** COUNTER: 3
The counter is incremented first by the event procedure Worksheet_Change in the Worksheet
module, then incremented again in the Workbook_SheetChange procedure in the ThisWorkbook
code module, and then incremented yet again by the App_SheetChange event procedure. Thus, the counter
gets a value of 3, not 1 as you might expect. If you are using events in a superior object, you must take care that the events in
the superior object do not duplicate work done in the subordinate procedure. Not properly managing events in subordinate and superior objects
can cause unexpected results, such as the result of 3 in the example above.
If you are writing your own class modules (see Introduction To Classes for an introduction to
working with and creating classes), you may want a class to raise an event when a specified action or condition occurs. This is
a fairly simple process. Note that only object modules (class modules, userform code modules, the sheet modules, and
the ThisWorkbook code module) may declare events. You cannot define, raise, or receive events in standard code modules. Also,
only object modules may declare variable WithEvents and therefore only object modules may receive
event triggers.
In the class that will create the events, you must first declare the events themselves with the
Public Event statement, such as:
Public Event EventName(Parameters, ByRef Cancel As Boolean)
Here, EventName is the name of the event. This should be a meaningful name. Parameters
is a list of any parameters that you want to pass to the class that is receiving this event, such as
X As Long, Y As Double, Z As String
This is optional. Finally, Cancel is an optional but useful parameter. It allows the class that is receiving
the event to respond back to the class that contains the event that the action in question should be cancelled. For example,
the Workbook_BeforeSave event has a Cancel parameter that allows you to cancel
the Save operation. While a Cancel parameter is entirely optional, it is a
nice touch that can add flexibility and a professional touch to your application.
Once you have declared your events, (one Public Event declaration for each event), you need
raise the event at the appropriate location in your class. Where you raise the events depends entirely on the context of the executing
code and what action or condition the event signifies. When I design commercial software, I use events extensively, using both a
BeforeSomeAction and AfterSomeAction event pair to notify any listener that my code is
about to carry out some action and to notify the listener than the action has been completed. If possible, I like to include
a Cancel parameter to allow the event listener to cancel a pending operation. However, you can use events
in any way you want.
You raise an event using the RaiseEvent statement. Once the event is declared, you trigger it with the
RaiseEvent statement. Both declaration and raising of the event EventName are
shown in the code below. Note that you cannot use Named Arguments when passing parameters to the
RaiseEvent procedure.
Public Event EventName(IDNumber As Long, ByRef Cancel As Boolean) Sub AAA() Dim B As Boolean Dim IDNumber As Long IDNumber = 1234 Cancel = False RaiseEvent EventName(IDNumber, Cancel) If Cancel = False Then Else End If End Sub
Once you have created your class with events, you need to write the code that will receive the event triggers. Note that only object modules
(class modules, a user form code module, a Sheet module, or the ThisWorkbook module — standard code modules cannot receive events) can receive
event messages. In a suitable object module, declare the event class using WithEvents:
Dim WithEvents XLEvents As CExcelEvents
At some point in your code, you will need to set the XLEvents variable to an instance of the
CExcelEvents class, with the code:
Set XLEvents = New CExcelEvents
Exactly when and where you put the object initialization code depends on what sort of module contains the event
declaration. While it is technically possible to put the initialization of the variable in another procedure, this is generally
a bad programming practice: it makes the code more difficult to debug and maintain. As a general rule, the code that initializes
the events variable should be in the same class as the events variable. Of course, the actual event code must reside in the
same object module as the events variable declaration. In a class module, the initialization would normally be in the Class_Initialize event.
For a user form, the code would go in the UserForm_Initialize event.
Private Sub XLEvents_EventName(IDNumber Long, Cancel As Boolean) Cancel = True End Sub
This page last updated: 23-March-2010
Роман72234 2 / 2 / 0 Регистрация: 04.02.2015 Сообщений: 48 |
||||
1 |
||||
04.02.2015, 16:59. Показов 4634. Ответов 6 Метки нет (Все метки)
Здравствуйте!!!
Все сохраняю. Книгу Excell открываю и при закрытие ничего не происходит, событие не работает. Добавлено через 1 минуту
0 |
Hugo121 6875 / 2807 / 533 Регистрация: 19.10.2012 Сообщений: 8,562 |
||||
04.02.2015, 17:31 |
2 |
|||
Какое именно событие хотите обработать? Можно всё делать проще… Добавлено через 5 минут
Кликаете по ячейкам.
0 |
Роман72234 2 / 2 / 0 Регистрация: 04.02.2015 Сообщений: 48 |
||||||||||||
04.02.2015, 18:23 [ТС] |
3 |
|||||||||||
Этот код который вы мне написали простой, я такие коды понял сразу и у меня они проходили.
в module
в module Эта книга
В чем ошибка?
0 |
SlavaRus 1121 / 229 / 36 Регистрация: 15.03.2010 Сообщений: 698 |
||||||
04.02.2015, 19:33 |
4 |
|||||
В чем ошибка? Не выделил память для объекта.
В приложении работающий пример на закрытие книги. Вложения
1 |
Hugo121 6875 / 2807 / 533 Регистрация: 19.10.2012 Сообщений: 8,562 |
||||
04.02.2015, 21:24 |
5 |
|||
Достаточно в любой книге в модуль книги написать это:
Сохранить, закрыть, открыть. И пользоваться в любой другой одновременно открытой книге.
1 |
2 / 2 / 0 Регистрация: 04.02.2015 Сообщений: 48 |
|
05.02.2015, 16:07 [ТС] |
6 |
Все ответы, которые мне были здесь предоставлены о решении данной проблемы хороши и изучены мной, но это не то что мне нужно. Все же методом проб и ошибок я нашел сам решение. После это у меня все заработало!!!
0 |
6875 / 2807 / 533 Регистрация: 19.10.2012 Сообщений: 8,562 |
|
05.02.2015, 16:52 |
7 |
У меня как видно выше всё заработало и без Excel и без Class module
0 |
Содержание
- How to handle events for Excel by using Visual Basic .NET
- Summary
- Create an event handler
- Create the Visual Basic .NET Automation client
- Test the code
- Troubleshooting
- References
- Обработка событий для Excel с помощью Visual Basic .NET
- Аннотация
- Создание обработчика событий
- Создание клиента службы автоматизации Visual Basic .NET
- Проверка кода
- Устранение неполадок
- Ссылки
- Vb.Net Excel automation row select event
- 1 Answer 1
How to handle events for Excel by using Visual Basic .NET
Summary
This step-by-step article describes how to handle Microsoft Office Excel 2003 and Microsoft Office Excel 2007 events from an Automation client that you develop by using Visual Basic .NET.
Create an event handler
You can create an event handler with Microsoft Visual Basic .NET 2003 or with Visual Basic .NET 2002 in either of the following ways. How you create an event handler with Visual Basic .NET depends on how you want to associate the event handler with events:
- Typically, you create an event handler by using the Handles keyword with the WithEvents keyword. When you declare a variable by using the WithEvents keyword, Visual Basic .NET automatically connects to the events of that object at run time. To handle a specific event for that object, add the relevant handler by using the Class list and the Method list of the Visual Studio .NET environment while you are in Code view.
- With the AddHandler keyword, Visual Basic .NET provides a second way to handle events. The AddHandler keyword and the RemoveHandler keyword, permit you to start and to stop event handling for a specific event dynamically.
Create the Visual Basic .NET Automation client
The following steps demonstrate how to use either way to handle Excel events from an Automation client that is developed by using Visual Basic .NET:
Start Visual Studio .NET 2002 or Visual Studio .NET 2003. On the File menu, click New, and then click Project. Under Visual Basic Projects, select Windows Application.
By default, Form1 is created.
Add a reference to the Microsoft Excel Object Library. To do this, follow these steps:
- On the Project menu, click Add Reference.
- On the COM tab, locate Microsoft Excel 11.0 Object Library, and then click Select.
- Click OK in the Add References dialog box to accept your selections. If you receive a prompt to generate wrappers for the libraries that you selected, click Yes.
On the Project menu, select Add Module. In the list of templates, select Module, and then click Open. Paste the following code in the new module:
Add another module to the project, and then paste the following code in the module:
Add the following to the top of both Module1.vb and Module2.vb:
Note The exact name for the Office namespace may differ depending on the version of the Office Primary Interop Assembly (PIA) that is registered in the Global Assembly Cache (GAC) when the reference is added to the solution. If you receive a build error message on this statement, check the name as it appears in Solution Explorer (under References) and then change the name as appropriate.
In Solution Explorer, double-click Form1.vb to display the form in Design view.
On the View menu, select Toolbox to display the Toolbox, and then add two buttons to Form1. Change the Text property of Button1 by typing Use WithEvents. Then change the Text property of Button2 by typing Use Delegates.
On the View menu, select Code to display the Code window for the form. Add the following code to the Click event handlers for the buttons:
Test the code
Press CTRL+ALT+O to display the Output window.
Press F5 to build and to run the program.
On the form, click Use WithEvents.
The program starts Excel and then creates a workbook with three worksheets.
Add any data to cells on one or more worksheets. Press the ENTER key after each change. Examine the Output window in Visual Studio .NET to verify that the event handlers are called.
On the form, click Use Delegates.
Again, the program starts Excel and then creates a workbook with multiple worksheets.
Add any data to cells on one or more worksheets. Press the ENTER key after each change. Examine the Output window in Visual Studio .NET to verify that the event handlers are called.
Quit Excel and then close the form to end the debug session.
Troubleshooting
When you test the code, you may receive the following error message:
An unhandled exception of type ‘System.InvalidCastException’ occurred in interop.excel.dll
Additional information: No such interface supported
References
For additional information about Microsoft Office development with Visual Studio .NET, visit the following Microsoft Developer Network (MSDN) Web site:
For additional information about automating Excel from Visual Basic .NET, click the following article number to view the article in the Microsoft Knowledge Base:
302094 HOWTO: Automate Excel from Visual Basic .NET to fill or to obtain data in a range by using arrays
Источник
Обработка событий для Excel с помощью Visual Basic .NET
Аннотация
В этой пошаговой статье описывается обработка событий Microsoft Office Excel 2003 и Microsoft Office Excel 2007 из клиента службы автоматизации, который разрабатывается с помощью Visual Basic .NET.
Создание обработчика событий
Обработчик событий можно создать с помощью Microsoft Visual Basic .NET 2003 или Visual Basic .NET 2002 одним из следующих способов. Способ создания обработчика событий с помощью .NET в Visual Basic зависит от того, как вы хотите связать обработчик событий с событиями:
- Как правило, обработчик событий создается с помощью ключевого слова Handles с ключевым словом WithEvents. При объявлении переменной с помощью ключевого слова WithEvents visual Basic .NET автоматически подключается к событиям этого объекта во время выполнения. Чтобы обработать определенное событие для этого объекта, добавьте соответствующий обработчик с помощью списка классов и списка методов среды Visual Studio .NET во время работы в представлении кода.
- С помощью ключевого слова AddHandler visual Basic .NET предоставляет второй способ обработки событий. Ключевое слово AddHandler и ключевое слово RemoveHandler позволяют запускать и останавливать обработку событий для определенного события динамически.
Создание клиента службы автоматизации Visual Basic .NET
Ниже показано, как использовать любой из способов обработки событий Excel из клиента службы автоматизации, разработанного с помощью Visual Basic .NET:
Запустите Visual Studio .NET 2002 или Visual Studio .NET 2003. В меню Файл выберите команду Создать, а затем выберите Проект. В разделе «Проекты Visual Basic» выберите приложение Windows.
По умолчанию создается Form1.
Добавьте ссылку на библиотеку объектов Microsoft Excel. Для этого выполните следующие действия:
- On the Project menu, click Add Reference.
- На вкладке COM найдите библиотеку объектов Microsoft Excel 11.0 и нажмите кнопку «Выбрать».
- Нажмите кнопку « ОК» в диалоговом окне «Добавление ссылок», чтобы принять выбранные параметры. Если появится запрос на создание оболок для выбранных библиотек, нажмите кнопку «Да».
В меню «Проект » выберите » Добавить модуль». В списке шаблонов выберите » Модуль» и нажмите кнопку » Открыть». Вставьте следующий код в новый модуль:
Добавьте в проект еще один модуль, а затем вставьте в него следующий код:
Добавьте следующий код в начало module1.vb и Module2.vb:
Примечание Точное имя пространства имен Office может отличаться в зависимости от версии основной сборки взаимодействия Office (PIA), зарегистрированной в глобальном кэше сборок (GAC) при добавлении ссылки в решение. Если в этой инструкции появляется сообщение об ошибке сборки, проверьте имя, которое отображается в Обозреватель решений (в разделе «Ссылки»), а затем измените имя соответствующим образом.
В Обозреватель решений дважды щелкните Form1.vb, чтобы отобразить форму в режиме конструктора.
В меню «Вид » выберите панель элементов , чтобы отобразить панель элементов, а затем добавьте две кнопки в Form1. Измените свойство Text объекта Button1, введя команду Use WithEvents. Затем измените свойство Text элемента Button2, введя команду «Использовать делегаты».
В меню « Вид» выберите «Код» , чтобы отобразить окно «Код» для формы. Добавьте следующий код в обработчики событий Click для кнопок:
Проверка кода
Нажмите клавиши CTRL+ALT+O, чтобы отобразить окно вывода.
Нажмите клавишу F5, чтобы выполнить сборку и запуск программы.
В форме щелкните «Использовать WithEvents».
Программа запускает Excel, а затем создает книгу с тремя листами.
Добавьте любые данные в ячейки на одном или нескольких листах. После каждого изменения нажмите клавишу ВВОД. Проверьте окно вывода в Visual Studio .NET, чтобы проверить, вызываются ли обработчики событий.
В форме нажмите кнопку «Использовать делегаты».
Снова программа запускает Excel, а затем создает книгу с несколькими листами.
Добавьте любые данные в ячейки на одном или нескольких листах. После каждого изменения нажмите клавишу ВВОД. Проверьте окно вывода в Visual Studio .NET, чтобы проверить, вызываются ли обработчики событий.
Закройте Excel и закройте форму, чтобы завершить сеанс отладки.
Устранение неполадок
При проверке кода может появиться следующее сообщение об ошибке:
Необработанное исключение типа System.InvalidCastException возникло в interop.excel.dll
Дополнительные сведения: такой интерфейс не поддерживается
Ссылки
Дополнительные сведения о разработке Microsoft Office с помощью Visual Studio .NET см. на следующем веб-сайте Microsoft Developer Network (MSDN):
Чтобы получить дополнительные сведения об автоматизации Excel из Visual Basic .NET, щелкните следующий номер статьи, чтобы просмотреть статью в базе знаний Майкрософт:
302094 HOWTO: автоматизация Excel из Visual Basic .NET для заполнения или получения данных в диапазоне с помощью массивов
Источник
Vb.Net Excel automation row select event
In a VB.Net Windows app I create an Excel object thru early binding and populate the spreadsheet with rows returned from SQL.
The intent is to allow the user to select 1 or more rows from the spreadsheet, capture the select event, and then retrieve all the data from the selected rows for further processing. I know this would be simple if I populated a datagridview with the SQL data but the users insist on Excel.
Although I have searched I haven’t been able to discover whether capturing the select event in .Net is possible.
If it is possible, please show me how or give me a link to a good article on this topic. If not, please let me know too.
Thanks for the run-down, Mike. I will do some more research, now that I have a good basis to start with. My users will be doing a variety of things in the spreadsheet: 1) selecting rows for further processing (routed operations); may be non-contiguous 2) adding rows if the the operation doesn’t appear in the input (unrouted operations) and then selecting them. 3) possibly adding column data (I’d like to restrict this function but the User is boss. )
I will experiment but can you tell me how to discriminate between a row selection and a column selection; i.e. selection changes I want to respond to and ones I don’t?
1 Answer 1
You are looking for the «SelectionChange» event. There are three related events for this: Worksheet.SelectionChange , Workbook.SheetSelectionChange , and Application.SheetSelectionChange .
I think that for your purposes, using Worksheet.SelectionChange is probably what you would want, since you already know which worksheet in which you are interested, but here’s an example using all three as an example:
You can run the above as follows:
In this example, if you change the selection on the active worksheet, then all three event handlers fire and you get 3 message boxes. It’s a bit annoying, lol, but it proves the point.
Of course you don’t have to use WithEvents to hook up your event handlers, you could use AddHandler instead:
Once your handler has been called, it can extract values using automation. You could use the Range.Value property to get the values from a single cell, or return a 2 dimensional range of values from a multi-cell range. Of course you could just run SQL again, once you know which Row(s) you want, based on the selection, but I just thought I’d point out that you can extract the cell values directly.
Hope this helps!
Edit: Update to Melody’s Reply
«Thank you so much for helping, Mike!»
«I need to get into the nitty-gritty of the Target.Address(External:=True) bit. I assume the target contains info on what was selected? Can you provide more info? Does it encapsulate a row number or numbers of the rows selected? Does it contain an index or item property that can be used to get at the column values? Does the External=True argument just say this is coming from non-managed code, or is my assumption incorrect?»
That was just an example to show how to report the address of the Range that was selected. Let’s look at the method signature for the Worksheet.SelectionChange event handler:
The event has one argument, which is the Target As Excel.Range argument. (The Application.SheetSelectionChange and Workbook.SheetSelectionChange events have a second argument stating on which worksheet the selection change occurred, but in the case of the Worksheet.Selection change event we already know on which worksheet the selection change occurred, so the parameter is omitted.)
The key is that you can make use of the Target As Excel.Range argument to determine what you want. To get the local address, which includes the Range address, but not the worksheet address (E.g. «A1:C3»):
To get the full path address (e.g. «[Book1.xls]Sheet1!A1:C3»):
To get the number of rows selected:
To get the Row Index on the worksheet (remember: Excel workshets use Base 1 addressing!) for the top row of the range:
To get the row index for the last row:
These are just some examples. You’ll have to make use of the Excel VBA help files (or Google) to get more information on the members of the Range class.
Due to the holiday I may not be able to respond to you right away, but I am grateful for the help.
Slacker. Just kidding, have a great weekend. 🙂
Источник
You are looking for the «SelectionChange» event. There are three related events for this: Worksheet.SelectionChange
, Workbook.SheetSelectionChange
, and Application.SheetSelectionChange
.
I think that for your purposes, using Worksheet.SelectionChange
is probably what you would want, since you already know which worksheet in which you are interested, but here’s an example using all three as an example:
Public Class ExcelEventHandlingClass
Dim WithEvents xlApp As Excel.Application
Dim WithEvents myWorkbook As Excel.Workbook
Dim WithEvents myWorksheet As Excel.Worksheet
Sub New()
xlApp = New Excel.Application
xlApp.Visible = True
myWorkbook = xlApp.Workbooks.Add
myWorksheet = CType(myWorkbook.Worksheets.Add, Excel.Worksheet)
End Sub
Private Sub xlApp_SheetSelectionChange( _
ByVal Sh As Object, _
ByVal Target As Excel.Range) _
Handles xlApp.SheetSelectionChange
MessageBox.Show( _
"xlApp_SheetSelectionChange: " & _
Target.Address(External:=True) & " was selected")
End Sub
Private Sub myWorkbook_SheetSelectionChange( _
ByVal Sh As Object, _
ByVal Target As Excel.Range) _
Handles myWorkbook.SheetSelectionChange
MessageBox.Show( _
"myWorkbook_SheetSelectionChange: " & _
Target.Address(External:=True) & " was selected")
End Sub
Private Sub myWorksheet_SelectionChange( _
ByVal Target As Excel.Range) _
Handles myWorksheet.SelectionChange
MessageBox.Show( _
"myWorksheet_SelectionChange: " & _
Target.Address(External:=True) & " was selected")
End Sub
End Class
You can run the above as follows:
Dim o As ExcelEventHandlingClass
Private Sub StartExample()
o = New ExcelEventHandlingClass
End Sub
In this example, if you change the selection on the active worksheet, then all three event handlers fire and you get 3 message boxes. It’s a bit annoying, lol, but it proves the point.
Of course you don’t have to use WithEvents
to hook up your event handlers, you could use AddHandler instead:
AddHandler xlApp.SheetSelectionChange, AddressOf xlApp_SheetSelectionChange
AddHandler myWorkbook.SheetSelectionChange, AddressOf myWorkbook_SheetSelectionChange
AddHandler myWorksheet.SelectionChange, AddressOf myWorksheet_SelectionChange
Once your handler has been called, it can extract values using automation. You could use the Range.Value
property to get the values from a single cell, or return a 2 dimensional range of values from a multi-cell range. Of course you could just run SQL again, once you know which Row(s) you want, based on the selection, but I just thought I’d point out that you can extract the cell values directly.
Hope this helps!
Mike
Edit: Update to Melody’s Reply
«Thank you so much for helping, Mike!»
No problem.
«I need to get into the nitty-gritty
of the Target.Address(External:=True)
bit. I assume the target contains info
on what was selected? Can you provide
more info? Does it encapsulate a row
number or numbers of the rows
selected? Does it contain an index or
item property that can be used to get
at the column values? Does the
External=True argument just say this
is coming from non-managed code, or is
my assumption incorrect?»
That was just an example to show how to report the address of the Range that was selected. Let’s look at the method signature for the Worksheet.SelectionChange event handler:
Private Sub myWorksheet_SelectionChange( _
ByVal Target As Excel.Range) _
Handles myWorksheet.SelectionChange
' Your code goes here...
End Sub
The event has one argument, which is the Target As Excel.Range
argument. (The Application.SheetSelectionChange
and Workbook.SheetSelectionChange
events have a second argument stating on which worksheet the selection change occurred, but in the case of the Worksheet.Selection
change event we already know on which worksheet the selection change occurred, so the parameter is omitted.)
The key is that you can make use of the Target As Excel.Range
argument to determine what you want. To get the local address, which includes the Range address, but not the worksheet address (E.g. «A1:C3»):
Dim localAddress As String = Target.Address
To get the full path address (e.g. «[Book1.xls]Sheet1!A1:C3»):
Dim localAddress As String = Target.Address(External:=True)
To get the number of rows selected:
Dim numRows As Integer = Target.Rows.Count
To get the Row Index on the worksheet (remember: Excel workshets use Base 1 addressing!) for the top row of the range:
Dim topRowIndex As Integer = Target.Row
To get the row index for the last row:
Dim lastRowIndex As Integer = Target.Rows(Target.Rows.Count).Row
These are just some examples. You’ll have to make use of the Excel VBA help files (or Google) to get more information on the members of the Range class.
Due to the holiday I may not be able
to respond to you right away, but I am
grateful for the help.
Slacker. Just kidding, have a great weekend.
Методы
Не будем повторяться. О методах было сказано уже достаточно. Любая процедура ( Sub ) или функция ( Function ), описанная в разделе методов класса, является его методом. Напомним синтаксис методов класса:
[Private | Public | Friend] [Static] Sub name [(arglist)] [statements] [Exit Sub] [statements] End Sub [Public | Private | Friend] [Static] Function name [(arglist)] [As type] [statements] [name = expression] [Exit Function] [statements] [name = expression] End Function
Роль всех ключевых слов в этих определениях уже пояснялась, чуть позже мы подробнее скажем о спецификаторе Friend, который могут иметь методы класса. Сейчас же отметим, что почти каждый класс, независимо от его специфики имеет некоторый «джентльменский» набор методов. В него входит:
- Один или несколько конструкторов класса, в том числе конструктор по умолчанию, заданный обработчиком события Initialize.
- Обычно, по паре процедур — свойств, определенных для каждого свойства класса.
- Метод, задающий печать свойств класса.
- Метод, позволяющий в диалоге с пользователем определять значения свойств класса, — своеобразный конструктор.
- Остальные методы, определяющие специфику класса.
Вернемся к проектированию класса Rational. Большую часть джентльменского его набора мы уже определили. Специфика этого класса определяется операциями над рациональными числами. Ограничимся четырьмя основными операциями — сложением, вычитанием, умножением и делением. Добавим к ним еще и метод печати дробей. Вот тексты этих методов, заканчивающих определение нашего класса:
Public Function Plus(a As Rational) As Rational Dim d As Integer, u As Integer, v As Integer Dim R As New Rational u = m * a.Знаменатель + n * a.Числитель v = n * a.Знаменатель d = nod(u, v) R.Числитель = u d R.Знаменатель = v d Set Plus = R End Function Public Function Minus(a As Rational) As Rational Dim d As Integer, u As Integer, v As Integer Dim R As New Rational u = m * a.Знаменатель - n * a.Числитель v = n * a.Знаменатель d = nod(u, v) R.Числитель = u d R.Знаменатель = v d Set Minus = R End Function Public Function Mult(a As Rational) As Rational Dim d As Integer, u As Integer, v As Integer Dim R As New Rational u = m * a.Числитель v = n * a.Знаменатель d = nod(u, v) R.Числитель = u d R.Знаменатель = v d Set Mult = R End Function Public Function Divide(a As Rational) As Rational Dim d As Integer, u As Integer, v As Integer Dim R As New Rational u = m * a.Знаменатель v = n * a.Числитель If v = 0 Then MsgBox ("деление на нуль невозможно") Else d = nod(u, v) R.Числитель = u d R.Знаменатель = v d Set Divide = R End If End Function Public Sub PrintRational() Debug.Print (m & "/" & n) End Sub
4.5.
Замечание:
Внутри класса при реализации операций над рациональными числами доступ к закрытым свойствам объекта, естественно, разрешен, и можно обращаться непосредственно к переменным m и n. Однако нельзя обратиться к закрытым свойствам параметров класса Rational, передаваемых методам Plus, Minus, Mult, Divide. Точно также, нельзя обратиться к свойствам локальных объектов (объекту R ), объявленных внутри методов. Единственный выход состоит в том, чтобы использовать для них Public Property Let и Public Property Get. Но тогда и вне класса можно использовать эти процедуры — свойства, изменяя, например, значение знаменателя дроби. Разрешать этого не хотелось бы.
На наш взгляд имеет место концептуальная некорректность поведения закрытых свойств. Внутри класса доступ к ним должен быть открыт не только для самого объекта, но и для объектов этого класса, заданных в качестве параметров методов класса. Точно также доступ должен быть открыт и для локальных объектов. Так поступают в большинстве языков программирования.
Приведем процедуру, которая работает с рациональными числами:
Public Sub WorkWithRational() Dim a As New Rational, b As New Rational, c As New Rational a.CreateRational 5, 7 b.CreateRational -12, -18 Set c = a.Plus(b) c.PrintRational Set a = c.Minus(b) a.PrintRational Set c = a.Mult(b) c.PrintRational Set b = c.Divide(a) b.PrintRational End Sub
При ее запуске получены следующие результаты:
Friend методы
Методы класса, в том числе процедуры — свойства Get, Let и Set могут иметь описатель Friend наряду с описателями Public и Private. Каждый из них задает свою область видимости метода, область программы, где метод доступен для вызова. Напомним, Private делает метод видимым только внутри класса, Public — делает метод доступным и область действия может распространяться не только на тот проект, в котором определен данный класс, но и на другие проекты. Чтобы управлять этим процессом, расширяя или сужая область видимости Public объектов, применяют специальные спецификаторы, например, опцию Option Private, которая закрывает модуль от других проектов. Методы с описателем Friend называют дружественными . Описатель Friend, действуя только на один метод, распространяет его область видимости на проект, в котором описан класс с Friend
методом. Но для других проектов дружественные методы не доступны, даже в том случае, когда там доступны Public методы. Так что метод дружит только со своим проектом и не более. Кто программировал на С++ и других языках, где есть наследование классов, знаком с дружественными методами, — там они дружат с классами — потомками родительского класса, в котором задан Friend метод. Вне семейства классов дружественные методы не доступны. На VBA роль семейства играет проект.
События
В мире объектов время от времени происходят события. Причиной их могут быть действия пользователя или реакция системы на ранее происшедшие события. При возникновении события, объекту с ним связанному, система посылает сообщение, при получении которого вызывается специальный метод — обработчик события. Эти методы, вызываемые специальным образом, и называются событиями. Итак, каждый класс имеет свойства, методы и события. Все объекты — экземпляры класса имеют одинаковый набор свойств, методов и событий, заданных определением класса. Методы у всех экземпляров класса одинаковы. Но, заметьте, обработчики событий у каждого экземпляра собственные! Впрочем, это же касается и значений свойств, — каждый экземпляр уникален. Все командные кнопки имеют метод Delete, который всегда выполняется одинаково, удаляя кнопку. Но щелчок по каждой кнопке, вызывающий событие Click, приводит к совершенно разным результатам. В этом сила событий, — поведение объектов становится уникальным.
VBA позволяет работать с объектами Office 2000, которым несть числа, и собственными объектами, классы которых мы умеем определять. Большинство из объектов Office 2000 не имеет событий, например, прославленный объект Range имеет множество свойств и методов, но ни одно из событий с ним не связывается. С другой стороны все объекты, предназначенные для организации интерфейса с пользователем, — формы, кнопки, списки и прочие объекты обладают набором событий. Но не только интерфейсные объекты имеют события. Объекты, задающие документы Word и Excel, страницы рабочих книг Excel, объекты Application также имеют встроенный набор событий. Для большинства из них мы подробно рассмотрели, как создаются обработчики событий для конкретных экземпляров. Напомним, есть специальный тип модулей, связанных с объектами, имеющими события, — сюда относятся модули форм, документов и другие. В окне кода этого модуля можно выбрать конкретный объект, выбрать для него возможное событие, построить заготовку обработчика события и наполнить затем ее содержанием. Подробнее об этом сказано в предыдущей лекции, посвященной модулям. Нам осталось рассмотреть две серьезные задачи:
- Есть важная группа объектов Office 2000, которая обладает встроенным набором событий. Однако эти объекты могут существовать в двух ипостасях, как объекты без событий и объекты с событиями With Events. В эту группу входят, например, объекты Application. По умолчанию эти объекты появляются как объекты, не имеющие событий, поэтому у них нет специального модуля, в окне кода которого можно было бы найти объект, найти список его возможных событий, создать обработчик события. Первая задача состоит в том, чтобы для таких стандартных объектов, классы которых обладают событиями, уметь создавать объекты с событиями ( With Events ), создавать для них модуль, в котором будут появляться заготовки обработчиков событий.
- Для собственных объектов нужно решить более сложную задачу, — необходимо не только уметь строить обработчики событий, но предварительно в классе определить сам набор возможных событий.
К решению этих двух важных задач мы сейчас и приступим.
Классы, объекты With Events и обработчики событий
Объекты Application являются наиболее известными объектами, имеющими события, но появляющимися по умолчанию как объекты без событий. С этих объектов и начнем и, поскольку технология включения событий во всех случаях одинакова, достаточно рассмотреть задачу создания обработчиков событий для объектов Application. Для того чтобы изложение сделать конкретным, будем полагать, что целью является создание обработчиков событий объекта Excel.Application. По ходу дела нам надо решить две основные задачи:
- Создать модуль, в котором можно строить обработчики событий:
- Создать сам объект Excel.Application With Events и связать его с текущим Excel приложением.
Заметьте, тут есть нюанс, — можно эту работу выполнять в самом Excel, после того, как открыта одна из его рабочих книг. Но можно выполнить эту работу и в другом приложении, например, в документе Word, что позволит обрабатывать возникающие события для всех открывающихся книг Excel, в том числе и первой. Выберем второй из этих вариантов.
Модуль класса с объектом WithEvents
Модуль, в котором будут появляться обработчики событий объекта Excel.Application, синтаксически является модулем класса, который нужно создать. Этот класс очень прост по своей структуре и при создании он содержит единственное закрытое свойство — соответствующий объект Application, объявленный со спецификатором WithEvents. Так что в нашем примере все описание создаваемого класса, который назовем, например, ExcelWithEvents сводится к одной содержательной строке:
Option Explicit 'Класс ExcelWithEvents Public WithEvents xlApp As Excel.Application
Как только такое свойство определено в классе, в окне кода этого модуля появляется объект xlApp, обладающий событиями, тут же можно выбрать из списка соответствующее событие, создать заготовку и затем наполнить ее содержанием. Взгляните на соответствующий рисунок:
Мы ограничились созданием двух обработчиков события NewWorkbook и OpenWorkbook. Вот их текст:
Private Sub xlApp_NewWorkbook(ByVal Wb As Excel.Workbook) MsgBox("Рад, что вы открыли книгу " & Wb.Name) End Sub Private Sub xlApp_WorkbookOpen(ByVal Wb As Excel.Workbook) MsgBox ("Рад, что вы открыли книгу " & Wb.Name) End Sub
Как видите, первая проблема решается достаточно просто и не требует введения новых средств. Но, обратите внимание, остается непонятным, что характеризует наш единственный объект xlApp, — конкретный экземпляр приложения, для которого создаются обработчики событий, или эти обработчики распространяются на множество экземпляров объектов класса ExcelWithEvents Но, поскольку объекты Application присутствуют в одном экземпляре, то не будем сейчас вдаваться в эти тонкости, позже мы еще вернемся к этому вопросу.
Объект WithEvents
Вторая проблема решается также довольно просто. Но здесь есть одна тонкость, которую следует понимать. Реально создаются два объекта, мы называем их двойниками. Один из них это обычный объект Excel.Application, другой — объект Excel.ApplicationWithEvents из класса ExcelWithEvents. Эти два объекта должны быть связаны одной ссылкой. Вот процедура, которая в нашем примере вызывается в документе Word в тот момент, когда пользователь решит начать работу с книгами Excel:
Public Sub CreateExApp() Const PathToMyDir = "e:O2000CD2000Ch4" 'Чистый Excel Dim MyEx As New Excel.Application 'Excel With Events Dim MyExWithEv As New ExcelWithEvents 'Связывание Set MyExWithEv.xlApp = MyEx 'Добавляем новую книгу и открываем существующую 'Обработчики событий New и Open работают! With MyEx .Visible = True .Workbooks.Add 1 .Workbooks.Add PathToMyDir & "BookOne" End With End Sub
Поверьте, обработчики событий действительно вызывались, как при создании новой книги, так и открытии книги, основанной на существующем шаблоне. Осталось сказать несколько слов о том, как это все могло выглядеть, если всю эту процедуру осуществлять в самом Excel. Обратите внимание, в нашем примере Excel Application с событиями появляется до того, как открыта какая либо из его книг. Если же работать в Excel, то описание объекта с событиями и связывание необходимо поместить в первую из открываемых книг. Мы проделали эту работу и в проекте книги BookOne создали класс ExcelWithEvents, а в одном из стандартных модулей создали процедуру:
Public Sub ConnectingWithEvents() Const PathToMyDir = "e:O2000CD2000Ch4" 'Excel With Events Dim MyExWithEv As New ExcelWithEvents 'Связывание Set MyExWithEv.xlApp = Excel.Application 'Добавляем новую книгу и открываем существующую 'Обработчики событий New и Open работают! With Application .Visible = True .Workbooks.Add 1 .Workbooks.Add PathToMyDir & "BookTwo" End With End Sub
Как видите, эта процедура отличается от процедуры CreateExApp лишь незначительными деталями. Возникает вопрос, как и когда запускать эту процедуру. Можно это делать по-разному, например, вызывать ее в обработчике события Click командной кнопки, можно вызывать ее в обработчике события OpenDocument. В последнем случае, обработчики событий объекта Application будут выполняться для всех книг, сразу после открытия книги BookOne.
Dave2
New Member
- Joined
- Apr 2, 2017
- Messages
- 2
-
#1
Application events are fired through the existence of a single object of type
«ClassApplicationEvents», for lack of a better class name.
This object variable dies when the application crashes.
I’m looking for a robust way to automatically recreate this object variable after a crash, hopefully without making the user invoke a macro.
Any ideas?
Excel Facts
Can a formula spear through sheets?
Use =SUM(January:December!E7) to sum E7 on all of the sheets from January through December
iliace
Well-known Member
- Joined
- Jan 15, 2008
- Messages
- 3,546
- Office Version
-
- 365
- 2016
- 2010
- Platform
-
- Windows
-
#2
Generally, you would create your own class and include an object using WithEvents keyword. E.g.
Code:
Dim WithEvents xlApp As Excel.Application
To test whether it has crashed, do a test
Code:
If xlApp Is Nothing Then
' ....
would that work?
As far as «robust», you would probably need to be recording any data or parameters along the way, which can get complicated.
Dave2
New Member
- Joined
- Apr 2, 2017
- Messages
- 2
-
#3
The problem with your proposed solution is that user interaction with the application which you desire to «listen to» via application events may already be going on before the test and possible resurrection of the object variable you describe has been carried out.
Jaafar Tribak
Well-known Member
- Joined
- Dec 5, 2002
- Messages
- 9,185
- Office Version
-
- 2016
- Platform
-
- Windows
-
#4
When a vbproject crash happens all variables are released including the variable holding the ‘ClassApplicationEvents’ class instance .
One way you could try is to reset the variable in a windows timer periodically say every second … Not an elegant solution but should accomplish what you want.
Last edited: May 17, 2017
You can use a class module to trap application events. Most of these events are the same as the workbook events, but they apply to all open workbooks, not just the particular workbook that contains the event procedures. For example, in a workbook there is a BeforePrint event that is triggered when you start to print anything in that workbook. At the application level, there is a WorkbookBeforePrint event that is triggered when any open workbook starts to print.
To see what application events are available, you first insert a class module into your project. The class module can have any valid module name. The one shown in Figure 16-4 has been named CAppEvents. You then type in the following variable declaration at the top of the module:
Public WithEvents xlApp As Application
The object variable name, xlApp, can be any valid variable name, as long as you use it consistently in code that refers to the class module, as a property of the class. The WithEvents keyword causes the events associated with the application object to be exposed. You can now choose xlApp from the dropdown at the top left of the module and then use the drop-down at the top right to see the event list, as shown in Figure 16-4.
- Figure 16-4
Choose the WorkbookBeforePrint event and extend the event procedure presented in Chapter 9, using the following code in CAppEvents:
Private Sub xlApp_WorkbookBeforePrint(ByVal Wbk |
As Workbook, _ |
Cancel As |
Boolean) |
Dim wks As Worksheet |
|
Dim sFullFileName As String |
|
Dim sCompanyName As String |
|
With Wbk |
|
‘Define footer data |
|
sCompanyName = «Execuplan Consulting» |
|
sFullFileName = .FullName |
|
‘Process each worksheet |
|
For Each wks In .Worksheets |
|
With wks.PageSetup |
|
.LeftFooter = sCompanyName |
|
.CenterFooter = «» |
|
.RightFooter = sFullFileName |
|
End With |
|
Next wks |
|
End With |
|
End Sub |
Unlike sheet and workbook class modules, the event procedures you place in your own class modules do not automatically function. You need to create an instance of your class module and assign the Application object to the xlApp property of the new object. The following code must be set up in a standard module:
Public xlApplication As CAppEvents
Sub TrapApplicationEvents()
‘Create instance of class module Set xlApplication = New CAppEvents
‘Assign the Excel Application object to the xlApp property Set xlApplication.xlApp = Application
End Sub
All you need to do now is execute the TrapApplicationEvents procedure. The WorkbookBeforePrint event procedure will then run when you use any Print or Preview commands, until you close the workbook containing the event procedure.
It is possible to terminate application event trapping during the current session. Any action that resets module-level variables and public variables will terminate application event processing, because the class module instance will be destroyed. Actions that can cause this include editing code in the VBE and executing the End statement in VBA code.
If you want to enable application event processing for all Excel sessions, you can place your class module and standard module code in Personal.xlsb and execute TrapApplicationEvents in the Workbook_Open event procedure. You can even transfer the code in TrapApplicationEvents to the Workbook_Open event procedure. However, you must keep the Public declaration of xlApplication in a standard module.
To illustrate, you can place the following code in the declarations section of a standard module:
Public xlApplication As CAppEvents
You can place the following event procedure in the ThisWorkbook module:
Private Sub Workbook_Open()
Set xlApplication = New CAppEvents Set xlApplication.xlApp = Application End Sub
Continue reading here: Embedded Chart Events
Was this article helpful?