automatically declare object class variable when creating new sheet in EXCEL - excel

How do I declare a - private - class variable, when a new sheet is created using the tab. It has to be done automatically. I presume it is a good idea to declare it by using the
Private Sub Workbook_NewSheet(ByVal Sh As Object) -event from the Wookbook object
Sub Workbook_NewSheet(ByVal Sh As Object)
Dim sh.privateVariableOfSheet As Integer
Declare New sh.privateVariableOfSheet2 As Integer
End Sub
Both above 'declarations' fails of course!
And for the completeness, how to refer to this variable from an ordinary module.

Another method is to use the CustomProperties collection of the Worksheet. For example:
Option Explicit
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Sh.CustomProperties.Add Name:=Sh.Name, Value:=99
End Sub
You can then use it later in a sub-routine:
Option Explicit
Sub Test()
Dim var As Variant
var = ThisWorkbook.Worksheets("Sheet13").CustomProperties(1)
MsgBox var
End Sub

Let take some class - clsFoo - which has a single property of type Range with a getter and setter:
Private m_rngSomewhere As Range
Public Property Get SomeRange() As Range
Set SomeRange = m_rngSomewhere
End Property
Public Property Set SomeRange(rng As Range)
Set m_rngSomewhere = rng
End Property
Now, in the Workbook code module you have:
a Public variable which we will set as a Dictionary
a Sub to instantiate the Dictionary - could be called from Workbook_Open or something
an event handler for Workbook_NewSheet
The event handler creates a new instance of clsFoo and sets its property as a Range from the new Worksheet, and then adds that to the dictionary (and checks if it was already there for some new reason).
Code in Workbook module:
Option Explicit
Public SheetFooDic As Object
Public Sub InitialiseSheetFooDic()
Set SheetFooDic = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Dim rng As Range
Dim cls As clsFoo
If Not SheetFooDic.Exists(Sh) Then
Set rng = Sh.Range("A1")
Set cls = New clsFoo
Set cls.SomeRange = rng
SheetFooDic.Add Sh, cls
End If
End Sub
This leaves you needing to simply use some Worksheet object as a key into the Dictionary in order to retrieve the Range you stored when the Worksheet was created. You can refer to the public Dictionary like this:
ThisWorkbook.SheetFooDic(ThisWorkbook.Worksheets("Sheet2")).SomeRange.Address
And get:
$A$1

You can store the address of the last selected cell as a string. So, asking for an object variable might be misleading. Either way, the easiest way is to declare a public variable (for example PrevCell As String, or As Range if you prefer or need the value) in each of your worksheets' code and set that variable in each sheet's Selection_Change event procedure.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
PrevCell = Target.Cells(1) ' or .Address
End Sub
Since you may find it useful to avoid recording selections of multiple cells my above procedure just records the address of the first cell which avoid errors that often crop up when a copy/paste action was performed on a sheet.
Now, when you insert a new sheet, don't use the Add method. Instead copy an existing sheet and clean it up the way you want. In this way the new sheet will already have the variable and the code that sets it.
I can't imagine your having need of the previous cell in any circumstance other than when the sheet is activated, but if my imagination is insufficient in this case, you might declare a global array with an element for each sheet, using the sheets' CodeName property for identification. This array would be set by the Selection_Change event procedure as demonstrated above, but when a sheet which isn't known to the array tries to register its latest selection it must be a new sheet and the array is extended to include it. The code to do so is inherited from other sheets by the same method described above.

Related

Using VBA in Excel - Static variable not retained when Multipage object has change event (changing pages)

I am having trouble with losing static variables within an Excel user form.
I have been working on a routine for excel. I am a (very) novice coder.
I am attempting to populate a cell range to an array. I have been able to do this without issue.
However, when I attempt to store the array as a *static * variable, the variable is not retained for as long as I want it to be.
I think the problem occurs when another page is selected in the multipage, the static variable is cleared.
Code is something like this:
Sub UserForm_Initialize ()
static myArray () as variant
dim myRange as range
set myRange = [namedrange]
myArray=myRange
msgbox myArray(0,0) 'this works fine
call OtherSub
end sub
sub OtherSub ()
msgbox myArray(0,0) 'this returns a blank
end sub
The first sub of code shows the array element just fine. The array element is blank in the second sub.
I have tried:
Declaring the array as a public variable, but this returns an error (I think that variables within user forms are private by default and cannot be changed).
using a very small variable (a simple string)
writing code in a module before opening the user form (variable is not retained).
I am aware that I can just write data to a cell range, but this would defeat the purpose. I was hoping to avoid multiple instances of reading large arrays from the worksheet.
This might explain it a bit clearer. Moving MyArray outside of the Procedure will set it's scope to a Module Level, making it usable through other subs within that module. You will generally want to keep the scope of your variables to the lowest level required. The other option would be to pass your variable as a parameter to your other procedure.
Option Explicit
Dim MyArray() As Variant ' Private Module Level Scope
Public ExampleVariable As String ' Public Module Level Scope (Not required, just showing an example.)
Sub UserForm_Initialize()
Dim myRange As Range ' Procedure Level Scope
Set myRange = [namedrange]
MyArray = myRange
MsgBox MyArray(0, 0) 'this works fine
Call OtherSub
End Sub
Sub OtherSub()
MsgBox MyArray(0, 0) 'this returns a blank
End Sub

VBA EXCEL: Apply sub to another worksheet from module

Trying to get a sub from a module to run within another worksheet. In a sense to stop using redundant ranges and keep it streamlined.
i.e.
-Module object-
Public sub method1()
{
Range("B4:B23") = ""
Range("C4:C23") = ""
'Empties these ranges...
}
-worksheet(s)-
sub project)
{
with sheet1 (or on any sheet 2,3,4,5... etc.)
Call module1.method1
'but this method only works on the module object, not in the context of the 'specified worksheet where it is needed
End with
Ideally to clear the data in ranges by using method1 in ANY worksheet. Every reference I tried just runs the module1 method without any effect or makes a useless reference to the method or worksheet. Just trying to save on code space by not writing direct references to every sheet which is formatted identical.
You can do this with a sub (ClearCells) that accepts a variable number of arguments via the ParamArray keyword. Then you can simply call the ClearCells sub and pass it the worksheet objects you want to clear the same ranges in, as in the DoClear sub. You can add more ranges as needed to the Union function in the GetRanges function.
Sub DoClear()
ClearCells Sheet1, Sheet3
End Sub
Sub ClearCells(ParamArray wkshts() As Variant)
Dim vWs As Variant
Dim ws As Worksheet
For Each vWs In wkshts
Set ws = vWs
GetRanges(ws).Clear
Next vWs
End Sub
Function GetRanges(ws As Worksheet) As Range
With ws
Set GetRanges = Union(.Range("B4:B23"), _
.Range("C4:C23"))
End With
End Function
Or assuming you are calling the method from the sheet you want to clear, you can just use ActiveSheet:
Public Sub Method1()
ActiveSheet.Range("B4:B23").Clear
ActiveSheet.Range("C4:C23").Clear
End Sub

Initializing cells and variables in worksheet when file is opened

Is it possible to initialize worksheet variables and cells on opening file? I have some cells which needs to be initiated and some variables also in my code.
I have found a way to initialize cells using the workbook_open sub. But initializing variables (defined in sheet1) is not possible. Is there an equivalent of Userform_Initialize for worksheets? I don't want to use worksheet_activate as I need this to perform only once. Is there a work around for this?
Public variable
If you declare a public variable in sheet1
Public strA As String
then you can set a value in Workbook_Open() with
Worksheets("Sheet1").strA = "init"
Sheet1.strA = "init" 'Alternative … If you prefer the VBA name of the sheet
Private variable
If it is a private variable then you need a init sub within sheet1 which you call from Workbook_Open()
sheet1
Private strA As String
Sub MyInit()
strA = "init"
End Sub
workbook
Private Sub Workbook_Open()
Worksheets("Sheet1").MyInit
Sheet1.MyInit 'Alternative … If you prefer the VBA name of the sheet
End Sub

Modify an existing VBA class

I would like to know if there is some way to add your own methods/properties to an existing VBA class (such Range, Charts, etc).
An example:
I would like the currently VBA class Worksheet have a specific method done by myself, something like:
'Worksheet methods
Public Sub LookFor (ByVal Value as String)
'My code
End Sub
Then I can call from any declared Worksheet class this function.
In class MyClass:
'MyClass members
Private pWS1 as Worksheet
Private pWS2 as Worksheet
Private pWS3 as Worksheet
'MyClass methods
Private Sub Class_Initialization()
Set pWS1 = Worksheets("WS1")
Set pWS2 = Worksheets("WS2")
Set pWS3 = Worksheets("WS3")
End Sub
Public Sub Example()
pWS1.LookFor("abc")
pWS2.LookFor("123")
pWS3.LookFor("def")
End Sub
Thanks!
There is no direct way to do this in VBA.
Best you can do is create a "wrapper" class which has a private Worksheet member, and expose that via a Sheet property. Add your "extension" methods to the class and have them operate on m_sheet.
Initialize your class by creating an instance of it and assigning a worksheet object to its Sheet property.
You can call your "extension" methods directly on the object, and any existing methods you'd access via the Sheet property.
Class MySheet:
Private m_sht As Worksheet
Public Property Set Sheet(ws As Worksheet)
Set m_sht = ws
End Property
Public Property Get Sheet() As Worksheet
Set Sheet = m_sht
End Property
Public Property Get CountHellos() As Long
CountHellos = Application.CountIf(m_sht.Cells, "Hello")
End Property
Test sub:
Sub Tester()
Dim sht As MySheet
Set sht = New MySheet
Set sht.Sheet = ActiveSheet
MsgBox sht.CountHellos '<< "extension" method
MsgBox sht.Sheet.Rows.Count '<< built-in object property
End Sub
Edit: you might be able to make the Sheet property the default for your class by following the steps outlined by Chip here: http://www.cpearson.com/excel/DefaultMember.aspx
May work to allow you to skip the Sheet property when working with instances of your class (but I've not tested this)
What you are looking for is called "Extension" methods in tradition Object Oriented Programming Languages.
See MSDN: Extension Methods (Visual Basic)
AFAIK, what you are looking for is not supported / available in traditional Visual Basic for Applications (VBA).
Here's an example of doing extension methods in Visual Basic .Net (VB.Net) from the MSDN source.
Step 1. Declare Extension method like so...
Step 2. Call extension method like so...

How do you store a worksheet reference in a VBA object?

This is going to seem trivial to those of you steeped in Excel object programming but it's beat me.
In the past, I've done the following in Excel's vba to restore the activesheet before exiting a subroutine..
sub foo()
dim cursheet
cursheet = ActiveSheet
someOtherSheet.activate
....
cursheet.activate
end sub
That works fine. I attempted to do something similar using objects and after several different approaches, wrote the following in a new Problem class...
''''''''''''''''''''''
' sheet property
''''''''''''''''''''''
Public Property Get sheet() As Worksheet
Set sheet = psheet
End Property
Public Property Let sheet(Value As Worksheet)
Set psheet = Value
End Property
Public Sub saveCursheet()
Me.sheet = ActiveSheet
End Sub
Public Sub activateSheet()
Me.sheet.Activate
End Sub
In my code, I invoke the methods this way...
Sub TallyQuizScore()
Dim curStudent As Problem
Set curStudent = New Problem
curStudent.saveCursheet
Worksheets("QuizTallies").Activate
...
curStudent.activateSheet
End Sub
When I attempt to execute curStudent.activateSheet, I get an error saying I need an object. So I reran the calling code and stepped through the saveCursheet method. I see the activesheet get stored but notice that the sheet object disappears as soon as I hit the setter's end property line. I don't know if that's an artifact of the debugger or if the sheet really does get tossed when I hit the end property line but whatever it is, the object is gone when I attempt to reactivate it when I'm done.
The frustrating thing is what I really wanted to write in my caller was
curStudent.sheet = Activesheet
and
curStudent.sheet.Activate
by somehow inheriting the builtin worksheet methods but that led to a rabbit's warren of code as I tried to make it work.
So three questions:
Why did the sheet I stored in saveCursheet disappear?
What do I need to change to make the code work?
What do I need to do differently from the above approach to make the curStudent.sheet = Activesheet and it's partner, curStudent.sheet.Activate approach work?
You need a module-level variable to store the value while your code is doing other things. Note that it's private.
Also, as caught by ja72, in the case of objects it's Set, not Let:
UNTESTED:
Private m_Sheet as Worksheet
Public Property Get Sheet() As Worksheet
Set sheet = m_Sheet
End Property
Public Property Set Sheet(Value As Worksheet)
Set m_Sheet = Value
End Property
Public Sub saveCursheet()
Me.Sheet = ActiveSheet
End Sub
Public Sub activateSheet()
Me.m_Sheet.Activate
End Sub

Resources