Storing meta information about Excel cells
Content
Background
While I was designing my Spill Manager for Excel add-in I needed a way for the add-in to quickly find spill ranges and to remember which spill ranges the add-in should manage (which spills it needs to watch for size changes). I needed to be able to:
- Locate a spill's top-left cell. This has to be reliable, if the user inserts or removes columns or rows or cell blocks, the spill's top-left cell still must be simple to find
- Store spill setttings:
- whether to copy formatting across columns or down rows
- whether to clean the formatting outside of the spill on a shrink
- whether there is a header row/column to account for
- whether there is a totals row/column
- whether the spill range needs to be updated on each change automatically
- the last size of the spill range, to be able to detect a change in dimensions and act accordingly
- Save and retrieve the information easily.
Method
At first I thought I'd use a set of range names per spill area, a range name for each of those settings. That posed a couple of issues:
- The add-in would potentially create many range names
- Maintaining separate range names for each spill area is hard.
Then I had an idea: I'll use the LET function for this purpose. After all, a range name is nothing more than a named formula. Any valid formula you can write in a cell, can also be put in a name. Spill manager now keeps one range name (local to the worksheet in question) for each spill, using a formula like this one:
Name: Sheet1!_SpillManager_Watch_19dd36c539f_5798b737
RefersTo formula:
=LET(rng,'Spill Manager Training'!$B$3,direction,"row",cleanupOnShrink,1,hasHeader,1,hasFooter,1,liveUpdates,1,lastRows,5,lastCols,6,rng)
Note how the final argument of the LET function repeats the first one, so the range name returns the spill range's top-left cell. This means I can use the range name in a formula exactly the same as if the name referred to the range directly. It also means VBA code like this "just works":
Set rng = Range("Sheet1!_SpillManager_Watch_19dd36c539f_5798b737")
And rng will now contain a direct reference to the spill anchor.
Implementation
All we need now is some code that parses the LET formula to extract the properties it stores. For that I devised a class module. The class uses property names which match those in the LET formula above exactly. This means we can use the CallByName VBA function to dynamically set the properties in a simple loop. Here's that class. I called it "clsGetMetaFromCell". The ExtractProperties routine does the heavy lifting in retrieving the settings from the formula.
Private mscleanupOnShrink As String '1
Private msdirection As String '"row"
Private mshasFooter As String '1
Private mshasHeader As String '1
Private mslastCols As String '6
Private mslastRows As String '5
Private msliveUpdates As String '1
Private moWatchedCell As Range
Private msmetaNameFormula As String
Private Sub ExtractProperties(rngName As String)
'Example formula in named range
'=LET(rng,'Spill Manager Training'!$B$3,direction,"row",cleanupOnShrink,1,hasHeader,1,hasFooter,1,liveUpdates,1,lastRows,5,lastCols,6,rng)
Dim items As Variant
Dim ct As Long
'Since the LET function returns the range, we can retrieve the spill cell by evaluating the formula:
Set WatchedCell = Range(rngName)
items = Split(msmetaNameFormula, ",")
'Now extract all properties we need, counting from the right
'Since the sheet name can contain a comma we extract only the other properties.
'The following starts from the two-but-last comma, which is the word "lastCols" in the formula:
For ct = UBound(items) - 2 To 3 Step -2
'We deliberately use the same property names in this class as we have in the LET named range
'So we can use CallByName to set the values.
'Because of this we need all properties to be string properties
'It is left to the user of the class to do conversions where needed
CallByName Me, CStr(items(ct)), VbLet, CStr(items(ct + 1))
Next
End Sub
Public Sub UpdateProperties2Name()
'This routine builds a new RefersTo formula and updates the name. That is relatively straightforward,
'so I left that for you to do :-). I don't think we can get around hard-coding the property names here though.
'One needs to call this routine from outside of the class after updating properties.
End Sub
Public Property Get cleanupOnShrink() As String
cleanupOnShrink = mscleanupOnShrink
End Property
Public Property Let cleanupOnShrink(ByVal newValue As String)
mscleanupOnShrink = newValue
End Property
Public Property Get direction() As String
direction = msdirection
End Property
Public Property Let direction(ByVal newValue As String)
msdirection = newValue
End Property
Public Property Get hasFooter() As String
hasFooter = mshasFooter
End Property
Public Property Let hasFooter(ByVal newValue As String)
mshasFooter = newValue
End Property
Public Property Get hasHeader() As String
hasHeader = mshasHeader
End Property
Public Property Let hasHeader(ByVal newValue As String)
mshasHeader = newValue
End Property
Public Property Get lastCols() As String
lastCols = mslastCols
End Property
Public Property Let lastCols(ByVal newValue As String)
mslastCols = newValue
End Property
Public Property Get lastRows() As String
lastRows = mslastRows
End Property
Public Property Let lastRows(ByVal newValue As String)
mslastRows = newValue
End Property
Public Property Get liveUpdates() As String
liveUpdates = msliveUpdates
End Property
Public Property Let liveUpdates(ByVal newValue As String)
msliveUpdates = newValue
End Property
Public Property Get WatchedCell() As Range
Set WatchedCell = moWatchedCell
End Property
Private Property Set WatchedCell(ByVal oNewValue As Range)
Set moWatchedCell = oNewValue
End Property
Public Property Get MetaName() As String
MetaName = msMetaName
End Property
Public Property Let MetaName(ByVal newValue As String)
msMetaName = newValue
End Property
Public Property Get metaNameFormula() As String
metaNameFormula = msmetaNameFormula
End Property
Public Property Let metaNameFormula(ByVal newValue As String)
msmetaNameFormula = newValue
ExtractProperties newValue
End Property
A Class doesn't do much without implementing it, so here is an example routine that reads all names pertaining to my add-in:
Dim nm As Name
Dim ws As Worksheet
Dim cMeta As clsGetMetaFromCell
Dim metas As Collection
Set metas = New Collection
For Each ws In Worksheets
For Each nm In ws.Names
If nm.Name Like "*_SpillManager_*" Then
Set cMeta = New clsGetMetaFromCell
cMeta.MetaName = nm.Name
'The next action also triggers the ExtractProperties routine inside clsGetMetaFromCell
cMeta.metaNameFormula = nm.RefersTo
'For demonstration purposes only:
With cMeta
MsgBox "Cell: " & .WatchedCell.Address(False, False, , True) & vbNewLine & _
"direction: " & .direction & vbNewLine & _
"footer: " & .hasFooter & vbNewLine & _
"header: " & .hasHeader & vbNewLine & _
"last cols: " & .lastCols & vbNewLine & _
"last rows: " & .lastRows & vbNewLine & _
"liveUpdates" & .liveUpdates
End With
'Store the list of properties so we can do something with them
metas.Add cMeta
End If
Next
Next
'Now loop through them to act upon them
For Each cMeta In metas
'If we made a change to any property like
cMeta.direction = "some other direction"
'then we need to update the name
cMeta.UpdateProperties2Name
Next
End Sub
See, I left something for you to do. I bet ChatGPT and its comrades are perfectly capable of filling that gap.

Comments