Monday, August 16, 2010

Playing with Excel Worksheet Change event

Workbook sheet change is a very good place to put your code if you want to track user actions. Depending on user action, you may want to write validations or database round trips. It can also be a substitute for "formulas" if written correctly. But it is easy to overlook scenarios and user interaction patterns, and make mistakes in coding.

A good way to begin is by creating sub routine to manage your operations on the Excel sheet. This is a global sub that can be called to set when some "controlled" system operations are going on. (System = your code).

The following code can be placed in a public module.

Option Explicit

Public bSystemUpdating As Boolean, oLastCalulation As XlCalculation
'Flag to indicate start of system update

Public Sub ManageSystemUpdate(bStartUpdate As Boolean)
If bStartUpdate Then
    
    If bSystemUpdating Then Exit Sub
    'Exit if update by system already started
    
    bSystemUpdating = True
    oLastCalulation = Application.Calculation
    ' Store Calculation mode for reseting when done
    
    Application.Calculation = xlCalculationManual
    
Else

    Application.Calculation = oLastCalulation
    bSystemUpdating = False
    
End If

Application.EnableEvents = Not bStartUpdate
'To disable Excel events like Worksheet change

Application.DisplayAlerts = Not bStartUpdate
'To disable Excel alerts like "Delete Sheet confirmation" etc

Application.ScreenUpdating = Not bStartUpdate
'To disable Excel screen updatation... Excel will update the screen on completion of your routine

End Sub

What next? Now we take the "Workbook_SheetChange" event and add our custom code into it. Remember, the "Workbook_SheetChange" event gets fired everytime any change happens to the cells, i.e., including your code. So it is important to manage the re-firing of these events in case you make updates. This can be done by setting a global variable to flag the start of a system update.

Option Explicit

Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Not Application.Intersect(Application.Range("Sheet1!A:A"), Target) Is Nothing Then
    'Only if any cell in the column "A" changes
    
    FetchFooValues Target
    
End If
End Sub

Public Sub FetchFooValues(oRange As Range)
Dim lCtr As Long

    ManageSystemUpdate True
    'Disable all events as well as all updates and calculation by Excel application
    
    For lCtr = 1 To oRange.Rows.Count
        Application.Range("Sheet1!B1").Offset(lCtr - 1).Value = FetchSingleFooValue(Trim(Application.Range("Sheet1!B1").Offset(lCtr - 1).Value))
    Next
    
    ManageSystemUpdate False
    'Enable all events
    
End Sub

Private Function FetchSingleFooValue(sInput As String)

'Do some operation here
FetchSingleFooValue = sInput

End Function

All done!

Thursday, August 12, 2010

Excel Template

Time and again, we create new Excel files with VBA, and end up rewriting our prized VBA codes (or copying pasting at best) that could be reused. Some of us have our own sleeveful of Excel files containing VBA codes.

There are some good ways of organizing (and growing) them. The most obvious way would be something like in Word... "normal.dot". In Excel unfortunately, such a thing does not exist. But we could do something close. Make a template file that could be used as a standard from the default "New Workbook" dialog.

Write the VBA codes in a new Excel file and save it as a Template, say "My_VBA_Template". The VBA code could be written anywhere as usual - as new modules or written as workbook/worksheet events.
Remember to save the file as a "Macro-enabled Template (.xltm)". You can now place the file in any of the following locations depending on the usage:
  1. C:\Program Files\Microsoft Office\Office12\XLSTART\  -or-
  2. C:\Documents and Settings\<user>\Application Data\Microsoft\Templates\
Now start Excel normally and open a new workbook. Choose the "My Templates..." from the access panel.

From here, choose the your template "My_VBA_Template".

A new file with the embedded macros would be created "My_VBA_Template1" (just like "Book1" normally).

For all future uses, a good thing is that, your templete would appear in the Recent Templates section and hence much easier to access.

Happy Excelling!

Wednesday, August 4, 2010

Unlock a VBA password protected Excel file

Ever felt the need to open a VBA protected excel file... maybe one of your old files that contained an excellent routine! How do you come out of that pain?

Important: This article is for educational purposes. Try this method for opening ONLY your own files, as I did too!

So how does Excel store the file contents - cell data including formulas and formats, conditional formatting, VBA code, etc. etc. Lets investigate. Create a new Excel file MyTest.xlsm and enter some dummy test data in the first sheet. Add some formulas and conditional formatting (if you want to really understand the details).
Let us now see how excel stores this data in the file. Open the file in notepad or a hex editor. Did you notice the first 2 characters? "PK". So Excel compresses its file contents. Now we know why there is not much difference if you compress an Office 2007 file.
Lets look into the compressed contents. Rename the file extension from .xlsm to .zip
Open the MyFile.zip file. Wow! its an extensive structure with xml files to store the workbook, worksheets, calculations, sharedstrings, etc.
This is how the XML of the Sheet1 looks
Lets explore more. Lets go back to our original file and add some VBA code to it.
Add a password and protect the VBA code.
Save the file and redo the same steps as earlier to open the xml file structure. We now have another XML file called vbaProject.bin. This is the file that I need to recover. Lets investigate further. Open this file in a Hex Editor (there are lots of free ones out there... the one I use is Hex Editor Neo at http://www.hhdsoftware.com/Products/home/hex-editor-free.html).
Search for the keyword "DPB" in the content. Remember to find it just above "[Host Extender Info]".
Once found, replace the string "DPB" to "DBx" and save the file. Now replace the edited "vbaProject.bin" file and place it back into the compressed file collection (replace with the old one). Rename the compressed file back to ".xlsm".

Try to open the file in Excel. It gives an error saying that the file contains an invalid key DBx. Hit Yes and proceed. The file opens. So far good. Now go to the VBA editor.
Next it gives another error "Unexpected Error". Proceed again by hittng OK
VBA Editor opens. Now try to open the module code. Again the "Unexpected Error". Dont lose heart... we are just there. Save the file and close it. We are all set. Your file is as good as new without the VBA password. Go check for yourself!

So Excel may NOT be encrypting the VBA file after all, rather it is only setting a flag to lock the VBA content. This is evident because we can still see the code in the vbaProject.bin file (that we had written in the VBA earlier).
Some more interesting bits. Check how Excel stores its file contents. Look into the file sharedStrings.xml. It stores the strings at a global level, not in the sheet itself. Also look into how it stores the formulas and formatting. Try out things like like dragging a cell content (like Area 1) into 5 cells (producing Area 1, Area 2, Area 3...) and see how it stores the values... using ranges.

That also says that, it may be better to access Excel data directly from these XML files rather than through the Excel application model. We could have much more control and simplified logic for storing (for instance in database), transporting (eg client machine to server), opening and displaying simple content. Many possibilities here...

Well, I was pretty pleased that today I learned something new... and an unlocked VBA code.