jwcolby
jwcolby at colbyconsulting.com
Thu Mar 5 22:49:38 CST 2009
Guys, I have majorly expanded the original quick introduction in preparation for eventually publishing it, probably in an eBook. I would appreciate comments and critiques on the new text. There is a lot of stuff in here, and I need your technical expertise to tell me if I am putting out bad information, or if there is anything that was left out which would make the introduction more informative or understandable. I have this in a word document. If anyone would like the word document for the purpose of marking it up and returning it to me let me know and I will send off line. Because of formatting, the Word document is much easier to read. In the meantime I am inserting the text below. A QUICK introduction to classes and events. CLASSES • A class is a module, but a module is not a class. • A class has properties and behaviors that a module does not. • A class is actually instantiated (loaded into memory) when a set statement is executed. In other words, an instance of the class is loaded into memory, and stays in memory until it is specifically unloaded (destroyed). A module is loaded into memory the first time any function in that module is called. • Like a module, a class can contain data (variables) and code. However the variables in a module can only contain one value at a time. A class can contain one value per class instance. • A class can be loaded into memory as many times as you want (limited only by the size of your memory) and each instance of a class can contain its own data values in its variables. • All instances of a particular class share code, but do not share variables. In other words, the code is only loaded into memory one time, but the variables are loaded once per class instance loaded. • Variables in a class instance header are assigned memory from the Heap. That memory remains assigned to that class instance until that class instance is destroyed (unloads). When a class instance unloads, the variable memory storage for that instance is returned to the Heap. • The code for a class loads when the first class instance loads. • A class instance (and in fact any object instance, including forms and controls) unloads from memory when the last variable holding a pointer to the object is set to nothing. When a class instance unloads, the memory storage for the data for that instance is returned to the system. When the last instance of a particular class unloads, then the memory storage for the code for that class is returned to the system. • A class has two built-in Events that fire, one as a class instance loads (Class_Initialize), and the other as the class instance unloads (Class_Terminate). • A class can define an event that it will Raise using the keyword Event followed by the name of the event it intends to raise, followed by any parameters it will pass. The class can then use the keyword RaiseEvent to raise the event. Events can pass parameters to the event sink. Modules cannot define or raise events. • The Code Behind Forms module in a form is a class module, but it is a special class in some ways as we will see later. Think of a class as a place to store information and code about some thing in the real world that we are modeling. Perhaps you have a clsPerson. That class has a bunch of variables called FirstName, LastName, SSN, ColorHair, ColorEyes, Gender, Birthdate etc. Load an INSTANCE of that class and fill in the data about John Smith, load another instance and fill in the data about Mary Smith etc. You might then have a piece of code that takes the birthdate and calculates the current age from that. The data and the code are all stored together in the class module. EVENTS Events can be thought of kind of like a radio transmission. The radio station transmits a signal, but they have no idea whether anyone is listening. In the case of events, this is called “Raising (or sourcing) an event”. The process of executing code for an event is called “sinking an event”. • Events can only be raised in classes. Modules cannot raise events. • Events can only be sunk in classes. Modules cannot sink events. • Events to be raised are defined in the header of a class using the following syntax: Event MyEventName([MyParam1 as type], [MyParam2 as type], …) • Events can only be raised in the body of subs and functions in classes using the syntax: RaiseEvent [MyParam1], [MyParam2], … • Parameters passed by RaiseEvent can be of any datatype understood by the VB interpreter including objects such as controls, forms, recordsets or even other class instances. • When an event is raised, program control passes to the event sinks in the order that their containing class is set. As an example, if a ClassA raises an event, and Class1, Class5 and Class10 sink that event, the order that the classes 1,5, and 10 execute their event sinks depend on the order that those three classes are instantiated (loaded using the Set keyword), from first to last instantiated. Unfortunately, the order was backwards in Access97, in other words, the last instance Set was the first instance to receive control. • If the Code Behind Form class of a form sinks an event of a control or other object, that event sink will always receive control first One thing not commonly understood is that an event is not even raised if the property of the event does not contain the text “[Event Procedure]”. Simply placing this text in a given event property, perhaps the OnClick or AfterUpdate event, will cause that event to start transmitting. As you will see, we use this fact to “hook” an event by programmatically placing this text string into an event property that we want to execute code for. For example if we want to sink an AfterUpdate event in our class for a combo named cboState, we simply execute the following code in the class’ initialization code: cboState.AfterUpdate = “[Event Procedure]”. Always remember though that just because an event is being raised doesn’t mean any code is being executed in response to the event. In the case of events, receiving the signal is called “sinking” the event. The code that receives control when an event is raised is called an event stub. The event stub looks like the following: Sub MyCbo_Click() End sub The format of the event stub is the keyword Sub followed by the name of the object raising the event, followed by an underscore _ followed by the name of the event. As we will soon see, the visual basic editor can automatically create the event stub for us using two combo boxes in the vb editor. If an event stub like the above is left empty, the next time that a compile is performed, the event stub will be deleted from the code. If you need to keep the stub around for the future, you need to place at least a comment in the stub to prevent it from being deleted. If no event stub exists for a given event for a given object, then the visual basic program control is never transferred and that event does nothing. If you have nothing but a comment in the event stub, then control will be transferred and the sub and end sub lines will execute. You can verify this by placing a breakpoint on the sub and end sub lines and causing the event to fire (be raised). Continuing with the radio metaphor, if someone is listening to that radio signal, then the person listening can do whatever they want with the signal they are receiving. They can do nothing at all, they can use it as a signal to launch an attack on an enemy, they can enjoy music, they can… The important thing to understand here is that what the listener does is up to the listener. Notice that the person broadcasting the signal (raising the event) doesn’t know or care whether anyone is listening (sinking the event), nor do they know or care what the listener (if they even exist) does with the signal (event sink). When you open a form, the form is constantly raising events. It raises OnOpen, OnClose, OnCurrent, BeforeUpdate, AfterUpdate, MouseMove etc etc. If those events are hooked, the events are raised whether or not anyone is listening. The form neither knows nor cares whether anyone is listening to (sinking) those events, it is simply raising these events so that if anyone is listening to (sinking) the events, they can do whatever they want when the events fire. When you place a control on the form, the control raises events under certain circumstances. When the control gets the focus it raises an OnFocus event, when it loses the focus it raises a LostFocus event, it raises a BeforeUpdate, AfterUpdate etc. Of course these events depend on what the user does, in other words they don’t happen unless the user manipulates the control in the correct manner, enters data for example. But notice that while the control always raises the event, it neither knows nor cares whether anyone is listening, nor does it know or care what the listener does with the event if anyone is listening (sinking the event). This is a critical thing to understand, that the object raising an event does not know nor care about the listener, nor what the listener does. The reason that this is critical is because it allows you to design an interface between objects which is totally asynchronous or disconnected. Have you ever built a subform and referenced a control on the parent form? Have you ever tried to open that subform by itself? It complains that it cannot find the control on the parent. The subform is has a “connected” interface to the parent, without the parent it cannot do its job correctly. The event “Raise/Sink” interface eliminates that dependence. The object raising the event does not depend on having a receiver of the event in order to function correctly. The receiver of events does not depend on the broadcaster existing in order to function, although of course it cannot do whatever it would do with the events if they are not being broadcast. But each side can be loaded and code can execute without the other side being loaded, without compile errors etc. The last thing to know is that regular modules cannot sink events, but a class can. A regular module cannot RAISE an event, but a class can. Classes are modules, but modules are not classes. Sinking events in classes Sinking events in classes is quite common. In order to sink an event in a class, you must dimension a variable of type SomeObject, and that object must source events. Furthermore you must use the keyword WithEvents. Dim WithEvents ctlCboState as ComboBox Any object which can raise events is known as an “automation object” which simply means that it is capable of raising events. The WithEvents keyword informs the VBA interpreter that this class may sink events for ctlCboState. In the VB Editor there are two combo boxes just above the code area. The combo on the left contains every object that can source events. Once you select an object in the left combo, the combo on the right contains a list of all of the events for the object in the left combo. Selecting an event in the right combo will cause an event stub to be inserted into the code area of the class, and the cursor to be placed into that event stub. If you dimension another object variable without the WithEvents keyword: Dim ctlTxtSSN as TextBox That object will not appear in the left combo of the editor and you will not be able to sink events for that object. Interestingly, I tried dimensioning a line control WithEvents. Dim WithEvents ctlLine as Line A line is an object, but it is not an automation object, i.e. it cannot raise events. The compiler did not throw an error when I compiled. ctlLine could be seen in the left combo box of the editor but it could not be selected, which makes sense as it has no events. In other words, I could not use the editor to select the line and create an event stub for ctlLine, but it did in fact appear in the left combo, and there was no compile error. On the other hand, trying to dimension WithEvents a normal variable of type long, string etc simply does not work at all, you get an immediate error as soon as you try to leave the line of code – Compile Error: Expected: Identifier. Sourcing events in classes Sourcing (raising) events in a class is much less common but can be quite useful as well. Sourcing an event simply means that the class becomes an automation object, can be dimensioned WithEvents in other classes, and the events raised by the class can be sunk in other classes. Sourcing events starts with telling VBA that the class is able to raise events. This is done with the keyword Event in the class header. The syntax is as follows: Event MyEventName([MyParam1 as AnyValidType], MyParam2 as AnyValidType]..) Placing a statement like that anywhere in the header of a class immediately makes the class an automation object. You can declare as many events as you wish. The parameters cannot be ParamArrays, nor can they be optional. Events cannot be declared to return values, however the parameters can be ByReference which can then be modified inside of the event sink, which is a form of return value. Summary In this lecture we have learned important terminology for working with Classes and events. We have what classes are, how they differ from modules, how they are instantiated, and how they are destroyed. We have discovered that classes can process events raised by other objects, and what events are, how to cause events to be raised, and how to sink events. -- John W. Colby www.ColbyConsulting.com