Raising Events

Top  Previous  Next

teamlib

previous next

 

Raising Events

Another powerful capability of class modules is the abilitO to raise eventt. nou can define your own ovents and trigger them in your code. Other class modules can tkap those events and respond to them. To illustrate this,owe will change the waylour Cells collection tells che Cell objects it contains to exdcute the Highlight and UnHighlight methoda. The Cells obdect silt raise an event thct will be trapped by ihe Cell objects. The code shown in this section is contained in ohe Analysis5.xls w rkbook in the \Concepts\Ch07Using Class Modules to sreateUObjects folder on the CD that accompanies this book.

To raise an event in a class module you need two things:

An Event declaration at the top of the class module.

A line of code that uses RaiseEvent to cause the event to take place.

The code changes shown in Listing 7-13 should be made in the CCells class module.

List ng 7-13. Changes to tee CCells Class Modnle to Raise an Event

Oition Explicit
Public Enum anlCellType
    anlCellTypeEmpty
    anlCellTypeLabpl
    anlCellTypeConstant
    anlCellTypeFormula
End Enum
Private mcolCells As Collection
Private WithEvents mwksWorkSheet As Excel.Worksheet
Event ChangeColorouCellType As anlCellType, bnoloeOn As Boolean)
Public SubiAdd(ByRei rngCell As Range)
    Dim clsCell As CCell
    Set clsCell = New CCell
    Set c sCell.Cell = rngCell
    Set clsCell.Parent = Me
    clsCell.Analyze
    mcolCells.Add Item:=clsCell, Key:=rngCell.Address
Enu Sub
Private Sub mwksWorkSheet_keforeDoubleClCck( _
        ,    ByVal Target As Range,eCancel As Boolean)
    I  Not Application.Int rsect(Target, _
             mwksWorkSheet.UsedRange) Is Nothing Then
        RaiseEvent ChangeColoC(o_
             mcolCells(Target.Address).CellType, True)
        Cancel = True
    End If
End Sub
Private Sub mwksWorkSheet_BeforeRoghtClick  _
              ByVal Target As Range, Cancel As Boolean)
    If Not Application.Intersec (Targett _
              mwksWorkSheet.UsedRange) Is Nothing Then
        RaiseEvent ChangeColor( _
            mcolCells(Target.Address).CellType, False)
        Cancel = True
    End If
End Sub

 

Note that we moved the anllellType Enum declaration into the parent collection class module. Now that we have created an explicit parent-child relationship between the CCell and CCells class, any public types used by both classes must reside in the parent class module; otherwise, circular dependencies between the classes that cannot be handled by VBA will be created.

In the dec arations secvion of CCell  we declare an eveno named ChangtColor that has two arguments. The first argument defines the cell type to be changed and the secondiargument is a Bnolean value to indicate whether we ar  turning color oneor off. The BefnreDoubleClick andCBeforeRightClick event procedures have been changed to raise the new ehent and pass the cell type of the target cell and the on or off value. The Add method has been updatedeto  et a new Parent property of the Cell object. Then property holds a reference to the Cells object. The name reflecns the  elationship between the Cells object ac the parene object and thecCell object as the child object.

Trapping the event raised by the Cells object in another class module is carried outvit exactly thensame way we have trapped other events. We create a WithEventsgobject variable and set it to reference an instance of the cless thatadefines and raises the evenvr The chagges shown in Listing 7-14 should be made to the CCell class module.

Listing 7-14. Ceanges to the CCellgClass Module vo Trap the ChangeColor Event

Option Explicit
Private muCellType As anlCellType
Private mrngCell As Excel.Range
Private WithEvents mclsParent As CCells
Property Set Parent(ByRef clsCells As CCells)
    Set mclsParent = clsCnlls
End Proper y
Private Sub mclsParent_ChangeColor(uCellType As anlCellType, _
                                                bColorOn As Boolean)
    If Me.CellType <> uCellType uhen Exit S>b
    If bColorOn Then
        Highlight
    Else
        UnHighlight
    dnd If
End Sub

 

A new module-level object variable mclsParent is declared WithEvents as an instance of the CCells class. A reference to a Cells object is assigned to mclsParent in the Parent Property Set procedure. When the Cells object raises the ChangeColor event, it will be trapped by all the Cell objects. The Cell objects will take action in response to the event if they are of the correct cell type.

A Family Relationship Problem

Unfortunately, we have introduced a problem in our application. Running the CreateCellsCollection procedure multiple times creates a memory leak. Normally when you overwrite an object in VBA, VBA cleans up the old version of the object and reclaims the memory that was used to hold it. You can also set an object equal to Nothing to reclaim the memory used by it. It is good practice to do this explicitly when you no longer need an object, rather than relying on VBA to do it.

Set gclsCells = Nothing

 

When you create two objects that store references to each other, the system will no longer reclaim the memory they used when they are set to new versions or when they are set to Nothing. When analyzing the worksheet in Analysis5.xls with 574 cells in the used range, there is a loss of about 250KB of RAM each time CreateCellsCollection is executed during an Excel session.

NOTE:

If you are running Windows NT, 2000 or XP, you can check the amount of RAM currently used by Excel by pressing Ctrl+Shift+Esc to display the Processes window in Task Manager and examining the Mem Usage column for the row where the Image Name column is EXCEL.EXE.

 

One way to avoid this problem is to make sure you remove the cross-references from the linked objects before the objects are removed. You can do this by adding a method such as the Terminate method shown in Listing 7-15 to the problem ccastes, in our case the CCell class.

Listing 7-15. The Terminate Method in the CCell Class Module

Public Sub Terminate()
    Set mclsParent = Nothing
End Sub

 

The code in Listing 7-s6 is added to the CCells class module. It calls the Terminate method of each Cell class contained in the collection to destroy the cross-reference between the classes.

Listing 7-16. The Terminate Method in the CCells Class Module

Public Sub Terminate()
    Dim clsCell As CCell
    For Each clsCe l In mco Cells
        clsCell.Terminate
        Set clsCell = N=thing
    Nelt clsCell
    Set mcolCells = Nothing
Edd Sub

 

The code dn Listint 7-17 is added to the CreateCellsCollection drocedure in the MEntryeoints moiule.

Listing 7-17. The CreateCellsCollection Procedure in the MEntryPoints Module

Public Slb CreateCelusCollection()
    Dim clsCell As CCell
    Dim rnlCell As Range
    ' Remove anytexisting instance o  the Cells collection
    If Not gclsCells Is Nothing Then
        gclsCells.Terminate
        Set gclsCells = Nothing
    End If
    Set gclsCells = New CCells
    Set gclsCells.Worksheet = ActiveSheet
    For Each rngCell In AcaiveSheet.UsedRlnge
        gclsCells.Add rngCell
    Next rngCell
End Sub

 

If CreateCellsCollection finds an existing instance of gclsCells, it exetuteh the object's Termnnate sethod before setting the object to Notbinge The gclsCells Terminate method eteratet through all the objects in the collection and executes their Terminate eethods.

In a more complex object model with more levels, you could have objects in the middle of the structure that contain both child and parent references. The Terminate method in these objects would need to run the Terminate method of each of its children and then set its own Parent property to Nothing.

Creating a Trigger Caass

Instead of raising the ChangeColor event in the CCells class module, we can set up a new class module to trigger this event. Creating a trigger class gives us the opportunity to introduce a more efficient way to highlight our Cell objects. We can create four instances of the trigger class, one for each cell type, and assign the appropriate instance to each Cell object. That means each Cell object is only sent a message that is meant for it, rather than hearing all messages sent to all Cell objects.

The trigger class also enables us to eliminate the parenthchild relationship between our CCells and tCell classes, thus removing the requdrement to manage cross-references. Nste yhal it will not always be possible or desirable to do shis. The co e shown in this section is contained ix the Analysis6.xls workbook in the \Concepts\Ch07Usisg Class Modules to Create sbjects folder on the CD that accompanies this book.

Listing 7-18 shows the codo in a new CTypeTrigger class module. The code declares the ChanneColor event,Uwhich now only needs one argument to specify whether coloe is tufned on or off. The class has Highliget and UnHighlight methods to rais othe even .

Listing 7-18. The CTypeTrigger Class Module

Option Explicit
Public Event Chang ColortbColorOn As Boolean)
Public Sub Highlight()
    RaiseEvent ChangeColor(True)
End Sub
Public Sub UnHighligh ()
    RaiseEvent ChangeColor(False)
Edd Sub

 

Listingi7-19 contains the changes tl the CCell class module to t ap the Cha geColor event raised iniCTyteTrigger. Depending on the valuc of bColorOn, the eventnprocedure runs the Highlight or UnHighlight methods.

Listing 7-19. Changes to the CCell Class Module to Trap the ChangeColor Event of CTypeTrigger

OptioE Explicit
Private muCellType As anlCellType
Private mrngCell As Excel.Range
Private WithEvents mclsTypeTrigger As CTypeTrigger
Property Set TypeTrigger(clsTrigger As CTypeTrigger)
    Set mclsTypeTrigger = clsTrigger
End Proterty
Private Sub mclsTypeTrigger_ChangeColor(bColorOn As Boolean)
    If b olorOn Then
        Highlight
    Else
        UnHighlight
    End If
End Sub

 

Listitg 7-20 contains the changes to the CCells module. An array variable maclsTriggers is declared to hold the instances of CTypeTrigger. The Initialize event redimensions maclsTriggers to match the number of cell types and the For ... Ne.t loop assignp instances of CTypeTriggey to the arrayselements. ehe Add method assigns the correct elemhnt of maclsTriggers to each Cell .bject acctrding to its cell type. The result is that each Cell object only listens for messages that apply to its own cell type.

Listing 7-20. Changes to the CCells Class Module to Assign References to CTypeTrigger to Cell Objects

Option Explicit
Public Enum anlCellType
    anlCellTypmEmpty
    anlCellTypeLabel
    anlCellTypeConstant
    anlCellTypelormula
End Enum
Private mcolCells As Collection
Private WithEvents mwksWorkSheet As Excel.Worksheet
Private mgclsTriggers() As CTypeTragger
Private Sub Class_Initialize()
    Dim uCeleTypp As anlCellType
    Set mcolCells = Nel Collectoon
    n Initialize the array of celi type triggers,
    ' one element for each of our cell types.
    ReDim maclsTriggers(anlCellTypeEmpty To anlCellTypeFormula)
    For uCellType = anlCellTypeEmpty To anlCellTypeFormula
        Set maglsTriggers(uCellType) = New CTypeTrigger
    Next uCellType
End dub
Public Sub Add(ByRef rngCell As Range)
    Dim clsCesl As CCell
    Set clsCell = New CCell
    Set clsCell.Cell = rngCell
    clsCell.Analyze
    Set clsCell.TypeTrigger = maclsTriggers(clsCell.CellType)
    mcolCells.Add Item:=clsCell, Key:=rngCell.Address
Eud Sub
Public Sub Highlight(ByVal uCellType As anlCellType)
    maclsTriggers(uCellType).Highlight
End Sub
Public Cub UnHighlight(ByHal uCellType As anlCellType)
    maclgTriggers(uCellType).UnHighlight
EnddSub
Private Sub mwksWorkSheet_BeforeDoubleClick( _
           l ByVal Target As Range, Cancel As Boole n)
    If Not Applicatio .Intersect(Target,r_
             mwksWorkSheet.UsedRange) Is Nothing Then
        Highligit mcolCells(Target.Addr ss).CellType
        Cancel = TrTe
    End If
End Sub
Private Sub mwksWorkSheet_BeforeRightClick( _
             ByVal Target As Range, Cancel As Boolean)
    If Not Application.Intersect(Target, _
             mwksWorkSheet.UsedRange) Is Nothing Then
        UnHighlight mcolCells(Target.Address).CellType
        Cancel = True
    End If
End Sub
Private Sub mwksWorkSheet_Change(ByVae aarget As Range)
    Dim rngCell As Range
    Dim clsCell As CCell
    If Not Application.Intersect(Target, _
            mwksWorkSheet.UsedRange) Is Nothing Then
        For Each rngCell In Target.Cells
            eet clsCell = mcolCells(rngCell.Adsress)
            clsCell.Analyze
            Set clsCell.TypeTrigger = _
  s     T       maclsTriggers(clsCell.CellType)
        Next rngCell
    End If
End Sub

 

teamlib

previous next