Error 91 - Object Not Set - excel

This is a continuation of posting #12772920 pertaining to error 91. Okay, here's the situation.
(1) If this line is commented out, then (2) produces error 91. If not commented out, the code runs fine.
(3) This should point the public obj shCompanies to the "Companies" sheet, shouldn't it? I'm confused why this line doesn't seem to set the shCompanies object the way that (1) does.
Option Explicit
Public wbCode As Workbook
Public Sub Main()
Dim tables As New CTables
Set wbCode = ThisWorkbook
Call SetExcelObjects
(2)Call tables.Indexing
End Sub
Public Sub SetExcelObjects()
Dim tables As New CTables
(3)Set tables.shCompanies = wbCode.Worksheets("Companies")
End Sub
---Class Module CTables
Option Explicit
Public shCompanies As Worksheet
Public Sub Indexing()
Dim rng As Long
Dim tables As New CTables
(1)Set tables.shCompanies = wbCode.Worksheets("Companies")
rng = tables.shCompanies.UsedRange.Rows.Count
End Sub

You need to declare tables as a public instance of CTables. Then you can act on it from different modules. Re-declaring it as you've been doing "wipes the slate clean" and causes the errors you are seeing. You certainly don't want to declare an instance of tables inside of CTables.
Note that best practice is to separate the instantiating tables from its declaration, therefor the line Set tables = New CTables in Main.
The below compiles and runs for me. I think it does what you expect:
Public wbCode As Workbook
Public tables As New CTables
Public Sub Main()
Set tables = New CTables
Set wbCode = ThisWorkbook
Call SetExcelObjects
Call tables.Indexing
End Sub
Public Sub SetExcelObjects()
Set tables.shCompanies = wbCode.Worksheets("Companies")
End Sub
'Class CTables
Public shCompanies As Worksheet
Public Sub Indexing()
Dim rng As Long
rng = shCompanies.UsedRange.Rows.Count
End Sub

Related

Public Property passing to other sheet modules

I have a VBA script that uses a Private Sub Open_Workbook() to initialize some arrays from one of the excel sheets to be used as lookup tables later. The Private Sub Open_Worbook() is placed in the ThisWorkbook module and it appears to do it's job when the excel workbook is open.
The arrays in the Open_Workbook() sub are Dim as Variants but I have not been able to "pass" them along to Sheet1 for example to be used in the Sub for that sheet. I have tried using an accessor similar to what was suggested at the following link:
Create and assign variables on Workbook_open, pass it to Worksheet_change
Here is my code following that suggestion.
Following code is in the ThisWorkbook module of the excel workbook:
Option Explicit
Private Test_Array() As Variant
Private Sub Workbook_Open()
' Code here to redim preserve Test_array and set elements of the array and other code that
' Added snippits of code to
Dim Test_Array() As Variant
For i = 1 To UBound(TestRange,2)
ReDim Preserve Test_Array(i - 1)
Test_Array(i-1) = TestRange(1,i)
Next i
End Sub
Public Property Get TestArray() As Variant
TestArray = Test_Array()
End Property
I was hoping that I would be able to use Thisworkbook.TestArray in Sheet1 to do some calculations on it but when I have a Msgbox ThisWorkbook.TestArray(0), I am getting a Subscript out of range error. I debugged the code and it appears that the Public Property Get TestArray(), Test_Array() is empty. What am I doing wrong? Can I not use Variant with Public Get Property?
I did confirm that the Test_Array in the Workbook_Open() Sub is indeed populated with the expected elements.
Edited: Added code for populating Test_Array
You have a Dim Test_Array() As Variant on module level and in the Private Sub Workbook_Open(). So you never populate Test_Array on the module level.
The following code is working for me
Workbook class module:
Option Explicit
Private Test_Array() As Variant
Private Sub Workbook_Open()
' Code here to redim preserve Test_array and set elements of the array and other code that
' Added snippits of code to
'Dim Test_Array() As Variant
Dim testrange
Dim i As Long
testrange = Sheet1.Range("A1:A4").Value
For i = 1 To UBound(testrange, 2)
ReDim Preserve Test_Array(i - 1)
Test_Array(i - 1) = testrange(1, i)
Next i
End Sub
Public Property Get TestArray() As Variant
TestArray = Test_Array()
End Property
Normal module
Option Explicit
Sub testit()
Debug.Print ThisWorkbook.TestArray(0)
End Sub
Reading on Lifetime of variables

When assigning value to class variable in VBA excel, the value is not set. How do I set a class declared variable?

I'm pretty new to VBA but I have been trying to figure out what is going wrong here. When I try to set a variable declared in my class module from a Sub in my module, the value isn't assigned for some reason and I can't figure out why. How do I get the variable table to go all the way through to the function call response = add_edit_data("fund_management.db", table)? It all compiles and runs, until it reaches the add_edit_data function which of course does not work without this variable. My Debug.Print checkpopup.table in the Sub returns nothing although I know the variable table from calling the Sub is set correctly as Debug.Print table returns the correct value when called from inside the Sub.
My module looks like this:
Private checkpopup As class_checkpopup
Public Sub checkbox_popup_fund(table)
Set checkpopup = New class_checkpopup
checkpopup.Show
checkpopup.table = table
Debug.Print checkpopup.table
End Sub
The class module is:
Public table As String
Private WithEvents check_box_popup As check_box
Private Sub Class_Initialize()
Set check_box_popup = New check_box
End Sub
Public Sub Show()
check_box_popup.Show
End Sub
Private Sub check_box_popup_Closed()
End Sub
Private Sub check_box_popup_Yes()
response = add_edit_data("fund_management.db", table)
End Sub
and I activate the eventhandler with:
Public Event Yes()
Private Sub check_box_yes_Click()
RaiseEvent Yes
End Sub
Thank you to Nicholas Hunter and Variatus for helping me out. I wasn't aware that it mattered to set the variable before showing the Form, but it does. I changed my module to:
Private checkpopup As class_checkpopup
Public Sub checkbox_popup_fund(table)
Set checkpopup = New class_checkpopup
Let checkpopup.table = table
checkpopup.Show
Debug.Print checkpopup.table
End Sub
and now my value carries through all the way to my function. Thank you very much for the help

Using a class module variable in a userform (Error '424' object required)

I have a class module which hosts a worksheet_change sub, and in that sub a Userform has to pop up. I want to use a number of variables from the class module in the Userform's code. Whatever I do, however, I can't get it to work.
I have tried to apply the method from this very lenghty guide, but to no avail. Other threads on SO weren't able to help me.
Private cell As Range
Public WithEvents m_wb As Workbook
Property Get cellr() As Range
Set cellr = cell
End Property
Property Set cellr(cellrange As Range)
Set cell = cellrange
End Property
Public Property Set Workbook(wb As Workbook)
Set m_wb = wb
End Property
Public Property Get Workbook() As Workbook
Set Workbook = m_wb
End Property
Public Sub m_wb_SheetChange(ByVal Sh As Object, ByVal Target As Range) 'simplified, but accurate
Application.EnableEvents = False
For each cell in Target
ReplaceTask.Show
Next cell
Application.EnableEvents = True
End Sub
In the userform_initialize macro, I need to be able to get the name of the m_wb workbook, as well as the cell (preferably as a range variable, otherwise just the address) in the For each cell in Target loop. For each variable in the code below I get
Error '424' object required
which shows the variables are not public..
Private Sub UserForm_Initialize()
Debug.Print cellrange.Address
Debug.Print cell.Address
Debug.Print cellr.Address
Debug.Print m_wb.Name
'....
I am positive it's my inability to understand how these properties work that's holding me back.. If someone could shine some light on what I am doing wrong, please!
To make this work it needs at least a public object variable of type of your class. And this object variable must be set to be a new instance of your class. This object variable then, and only this object variable, is the public accessable instance of your class.
Example:
Let your class be named clsWorkbook and having following code:
Option Explicit
Private m_cell As Range
Private WithEvents m_wb As Workbook
Property Let cell(cellrange As Range)
Set m_cell = cellrange
End Property
Property Get cell() As Range
Set cell = m_cell
End Property
Public Property Let Workbook(wb As Workbook)
Set m_wb = wb
End Property
Public Property Get Workbook() As Workbook
Set Workbook = m_wb
End Property
Private Sub m_wb_SheetChange(ByVal Sh As Object, ByVal Target As Range) 'simplified, but accurate
Application.EnableEvents = False
For Each m_cell In Target
ReplaceTask.Show
Next m_cell
Application.EnableEvents = True
End Sub
Let your UserForm named ReplaceTask having following code:
Option Explicit
Private Sub UserForm_Initialize()
Debug.Print oWB.Workbook.Name
Debug.Print oWB.cell.Address
End Sub
And in a default Module have following code:
Option Explicit
Public oWB As clsWorkbook
Public Sub test()
Set oWB = New clsWorkbook
oWB.Workbook = ThisWorkbook
End Sub
Now, after Sub test() was run, do changig something in a worksheet in the workbook the code is in. This should trigger the Sub m_wb_SheetChange(ByVal Sh As Object, ByVal Target As Range) of your class object oWB then, which shows the user form which also can access oWB.Workbook.Name and oWB.cell.Address.
Because of the discussion about the need of a global instance of clsWorkbook lets have a complete example which one can reconstruct and which shows how the clsWorkbook can be a private class member:
Let your class be named clsWorkbook and having following code:
Option Explicit
Private m_cell As Range
Private WithEvents m_wb As Workbook
Property Let Cell(cellrange As Range)
Set m_cell = cellrange
End Property
Property Get Cell() As Range
Set Cell = m_cell
End Property
Property Let Workbook(wb As Workbook)
Set m_wb = wb
End Property
Property Get Workbook() As Workbook
Set Workbook = m_wb
End Property
Private Sub m_wb_SheetChange(ByVal Sh As Object, ByVal Target As Range) 'simplified, but accurate
Application.EnableEvents = False
Dim frm As ReplaceTask
For Each m_cell In Target
Set frm = New ReplaceTask
frm.Init Me
frm.Show
Next m_cell
Application.EnableEvents = True
End Sub
Let your UserForm named ReplaceTask having following code:
Option Explicit
Private m_ParentClass As clsWorkbook
Friend Sub Init(ByVal p As clsWorkbook)
Set m_ParentClass = p
Me.Caption = p.Workbook.Name & " : " & p.Cell.Address
End Sub
And in default class module ThisWorkbook have following code:
Option Explicit
Private oWB As clsWorkbook
Private Sub Workbook_Open()
Set oWB = New clsWorkbook
oWB.Workbook = Workbooks.Open("P:/Mappe1.xlsx")
End Sub
Now the clsWorkbook gets instantiated while workbook open and is a private member of ThisWorkbook and it's workbook member is the workbook which was opened addditional. There the SheetChange are listened by the clsWorkbook oWB instance.
And because the ReplaceTask user form gets instantiated in clsWorkbook and was given the class instance as parameter, this user form knows the class members too.
The user form and the class with the event handler are two different scopes. You cannot expect to be able to refer to members of a different scope without qualifying that scope. The code in your UserForm_Initialize interprets cellrange and cellr as local variables declared in the user form itself. You don't have such variables declared in the user form, and you are not using Option Explicit, so instead of a compile time error you are getting a runtime error 424 when the code implicitly assumes it's Dim cellrange As Variant which was never initialized and is therefore Empty.
To fix the problem, you need to tell the instance of the user for which instance of the event-handling class it should get the properties from. For that it would be enough to put this in the UserForm:
Private m_ParentClass As ThatClassThatCreatesForms
Friend Sub Init(ByVal p As ThatClassThatCreatesForms)
Set m_ParentClass = p
End Sub
and change the For Each loop in the parent class as:
For each cell in Target
ReplaceTask.Init Me
ReplaceTask.Show
Next cell
You have to have a separate "Init" method because VBA classes cannot have constructors with parameters.
Then the code in ReplaceTask can use m_ParentClass.cell, m_ParentClass.Workbook etc. But you cannot do that from UserForm_Initialize because Init has not been called yet. It is not a problem though, simply move the code from UserForm_Initialize into Init.
To take it one step further, I would advice that you stop are using the implicit form instance. It is a good practice to create the instances manually:
For each cell in Target
Dim f As ReplaceTask
Set f = New ReplaceTask
f.Init Me
f.Show
Next cell

automatically declare object class variable when creating new sheet in 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.

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