Excel VBA Object Variable Not Set Error 91 - vba

I've been having trouble with a specific line of VBA code today. I keep getting error messages about "Object variable not set..." which is VBA error 91.
The following code is where the error occurs and is located in the modules folder as mFactory
Public Function CreateInterface(InterfaceWB As Workbook, SourceFilepath As String, SourceBookPass As String) As CInterface
Dim NewInterface As CInterface
Set NewInterface = New CInterface
NewInterface.InitiateProperties InterfaceWB:=InterfaceWB, SourceFilepath:=SourceFilepath, SourceBookPass:=SourceBookPass <------Error Here
Set CreateInterface = NewInterface
End Function
This method is called on workbook open from ThisWorkbook:
Public Interface As CInterface
Private Sub Workbook_Open()
Dim InterfaceWB As Workbook
Dim SourceFilepath As String
Dim SourceBookPass As String
Set InterfaceWB = ThisWorkbook
'Change this variable if the location of the source workbook is changed
SourceFilepath = "C:\file.xlsx"
'Change this variable if the workbook password is changed
SourceBookPass = "password"
Set Interface = mFactory.CreateInterface(InterfaceWB, SourceFilepath, SourceBookPass)
End Sub
And the InitiateProperties method called in the mFactory module is implemented in the class module CInterface:
Sub InitiateProperties(InterfaceWB As Workbook, SourceFilepath As String, SourceBookPass As String)
pInterfaceWB = InterfaceWB
pSourceFilepath = SourceFilepath
pSourceBookPass = SourceBookPass
End Sub
My VBAProject structure is as follows:
VBAProject
Microsoft Excel Objects
Sheet1
ThisWorkbook
Modules
mFactory
Class Modules
CInterface
One thing I did is change the CInterface instancing to PublicNotCreatable because I was getting an error about passing private arguments to public functions. I'm trying to use a "constructor" to create one instance of the CInterface class to use globally in the VBA project. Why is the object not set at the line I'm getting the error?

Ths error occurs when you try to use or assign an object variable without setting it, in this case the pInterfaceWB.
Sub InitiateProperties(InterfaceWB As Workbook, SourceFilepath As String, SourceBookPass As String)
pInterfaceWB = InterfaceWB
Should be
Set pInterfaceWB = InterfaceWB

Related

Range.Formula error with user defined functions

Hello guy I have a user defined function inside VBA
Function clean(word As String, ParamArray characters() As Variant) As String
For i = 0 To UBound(characters)
word = Replace(word, characters(i), "")
Next i
clean = word
End Function
whenever I try to use it in another subroutine like that
Sub prova()
Dim wb As Workbook
Dim wsB As Worksheet
Set wb = ThisWorkbook
Set wsB = wb.Sheets("Bond Holdings")
wsB.Range("R3").Formula = "=clean(""dfsduuu"",""u"")"
End Sub
I get runtime error 1004. Could you guys help me figure out why? this is driving me crazy.
Thank you
Excel has a built-in function called CLEAN. You have a name-clash. If you call your function e.g. cleaner, it will work as expected.

How to use Workbook_BeforeClose from a custom module

I am trying to prompt the user when he tries to close a workbook like this
Private Sub Workbook_BeforeClose(Cancel as Boolean)
MsgBox "Changes has been detected. Do you want to export the data ?"
End Sub
I know that this code need to be placed in ThisWorkbook module.
Is there a way to do that from my custom module ? I need to add this functionality to multiple workbooks used by my client as a part of up-gradation, which is usually done by replacing old modules with new modules.
You can use the Application.VBE object and Workbook.VBProject to modify a file's VBA. Note however that it requires that the Excel performing the upgrade has to have the setting "Trust access to the VBA project" toggled on (it can be found in the Trust center under the tab Macro settings). When not needed anymore, it's an option best left off though for security reasons.
There is a way how you can Import the ThisWorkbook Module. I wrote some Code for that a long Time ago.
So how does it work.
First you have to Export the ThisWorkbook Module. Right click on the Module and Export.
Save the ThisWorkbook.cls on the Server where you have your other Module's or send it with the Modules (Like how you do the Upgrade of the other Modules)
Open the ThisWorkbook.cls File with a Editor (Like Notepad++)
And Delete The First Rows. They Look like This.
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Execute the UpdateThisDocument Subrutine.
The Only Question how have to answer yourself is how you will Execute The Code ^^ (I wrote en Extern Updater that Executed the Code)
Sub UpdateThisDocument()
Dim ModuleName As String
ModuleName = "DieseArbeitsmappe"
Dim aDoc As Workbook
Set aDoc = ThisWorkbook
Dim strPath As String
'Put here the Path to the Folder where the cls File of This Workbook is.
strPath = "C:\Users\z002mkvy\Desktop\"
With aDoc
If ModuleExists(ModuleName) = True Then
Call clsLoeschen
End If
'
With ThisWorkbook.VBProject
.VBComponents(ModuleName).CodeModule.AddFromFile _
strPath & "\DieseArbeitsmappe.cls"
Fehler:
End With
End With
End Sub
Private Function ModuleExists(ModuleName As String) _
As Boolean
On Error Resume Next
ModuleExists = Len(ThisWorkbook.VBProject _
.VBComponents(ModuleName).Name) <> 0
End Function
Private Sub clsLoeschen()
Dim modcls
Dim awcl As Integer
On Error Resume Next
Set modcls = ThisWorkbook.VBProject.VBComponents.Item("DieseArbeitsmappe")
awcl = modcls.CodeModule.CountOfLines
modcls.CodeModule.DeleteLines 1, awcl
Set modcls = Nothing
End Sub
I hope This can Help you

Adding reference to VBA

I am trying to automatically add a reference while running a sub.
But I get "User defined-type not defined" for project As VBAProject
What's wrong?
Sub tester()
Dim SolverPath As String
SolverPath = Application.LibraryPath & "\SOLVER\SOLVER.XLA"
Dim wb As Workbook
Set wb = ThisWorkbook
Dim project As VBAProject
project = wb.VBProject
wb.project.References.AddFromFile SolverPath
End Sub
VBAProject is actually a special type of library, not a variable type. If you hit F2 to bring up the Object Browser, you can select it from the library dropdown. So you can't create variables of type VBAProject. However, it's not necessary to, nor for ThisWorkbook, unless you just want a shorter alias. Just use the workbook's VBProject property to gain access to the current project instance and add/remove components. Your code could be simplified to:
Sub tester()
Dim SolverPath As String
SolverPath = Application.LibraryPath & "\SOLVER\SOLVER.XLA"
ThisWorkbook.VBProject.References.AddFromFile SolverPath
End Sub

Declare a Workbook as a Global variable

I am starting to write a code that will become applicable to multiple workbooks, but always uses the same reference workbook. The code will have many subs, and as I am trying to avoid to dim a variable to the reference workbook in every sub I would like to declare them Global.
First I had:
Global Locations As Excel.Workbook
Set Locations = Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx")
Which gave me:
"Compile error: Invalid outside procedure"
After some googling I found the following bit of code somewhere:
Public Const Locations As Excel.Workbook = "Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx")"
Which gave me:
"Compile error: Expected: type name"
Edit:
Using:
Public Const Locations As Excel.Workbook = "Workbooks.Open('M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx')"
(Single quotation marks within the Workbooks.Open statement) results as the same error as when using double quotation marks.
Who knows what I am doing wrong?
Edit2:
I also tried to declare the variables in the "ThisWorkbook", following this answer using:
Private Sub Workbook_Open()
Dim Locations As Excel.Workbook
Dim MergeBook As Excel.Workbook
Dim TotalRowsMerged As String
Locations = Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx")
MergeBook = Workbooks.Open("M:\My Documents\MSC Thesis\Italy\Merged\DURUM IT yields merged.xlsm")
TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count
End Sub
But then it returns an
"Object Required"
within my module.
Edit3:
I now have this which works, but has the downside of having to copy the SET lines into every Sub, there has to be a better way to do this?
Global Locations As Workbook
Global MergeBook As Workbook
Global TotalRowsMerged As String
Sub Fill_CZ_Array()
Set Locations = Application.Workbooks("locXws.xlsx")
Set MergeBook = Application.Workbooks("DURUM IT yields merged.xlsm")
TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count
I think the most universal way for workbook global variable would be creating a module with a Public Property Get procedure. You can refer to it without calling any code first, and you don't have to worry if the file is open or not.
Here is the sample module code for one of the variables:
Private wLocations As Workbook
Public Property Get Locations() As Workbook
Const sPath As String = "M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx"
Dim sFile As String
If wLocations Is Nothing Then
'extract file name from full path
sFile = Dir(sPath)
On Error Resume Next
'check if the file is already open
Set wLocations = Workbooks(sFile)
If wLocations Is Nothing Then
Set wLocations = Workbooks.Open(sPath)
End If
On Error GoTo 0
End If
Set Locations = wLocations
End Property
You can use it anywhere in the code as a global variable:
Sub Test()
Debug.Print Locations.Worksheets.Count
End Sub
Your question implies that you want a global workbook constant, not a variable. Because VBA doesn't allow objects to be initialised outside of a procedure, you can't have an object constant. The best you can do is have a public workbook variable that's initialised in an event.
You can declare a global variable, but you can't execute code to assign a value outside of a procedure:
Public myBook As Excel.Workbook
Sub AssignWorkbook()
Set myBook = Workbooks.Open("C:\SomeBook.xlsx") '// <~~ valid, inside sub
End Sub
Sub TestItWorked()
MsgBox myBook.Name
End Sub
So in a normal module you could have:
Public myBook As Excel.Workbook
And in your Workbook_Open() event:
Private Sub Workbook_Open()
Set myBook = Workbooks.Open("C:\SomeOtherBook.xlsx")
End Sub
Then you can use myBook elsewhere in your code without having to re-assign it.
It might be worth having a look at Chip Pearson's article about variable scope in VBA here
what you want is some sort of Factory with static properties, for example in a separate module
mFactoryWkbs
Private m_WkbLocations As Workbook
Private m_WkbMergeBook As Workbook
Public Property Get LOCATIONS() As Workbook
If m_WkbLocations Is Nothing Then
Set m_WkbLocations= Workbooks.Open("wherever")
End If
Set LOCATIONS = m_WkbLocations
End Property
Public Property Get MERGEBOOK () As Workbook
If m_WkbMergeBook Is Nothing Then
Set m_WkbMergeBook = Workbooks.Open("wherever")
End If
Set MERGEBOOK = m_WkbMergeBook
End Property
To use, just call the property where & when you need it, no extra variables (or Sets for them) required.
TotalRowsMerged = MERGEBOOK.Worksheets("Sheet1").UsedRange.Rows.Count
This is the best I can come up with until now. The result is that there is now only one place to change the name of the file, however I still need to copy the SET function within every subroutine. Not completely ideal yet, but better then nothing.
Public Const DESTBOOK = "DURUM IT yields merged.xlsm"
Global Locations As Workbook
Global MergeBook As Workbook
Global TotalRowsMerged As String
Sub Fill_CZ_Array()
Set Locations = Application.Workbooks("locXws.xlsx")
Set MergeBook = Application.Workbooks(DESTBOOK)
TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count
Whenever I run into this, I declare wb as a public constant string:
public wb as string = "c:\location"
Then, throughout the code in the project, you can refer to
workbooks(wb).anything
This is the sort of thing I usually do when I have global variables that need to be properly initialized:
In a general code module put the following code:
Public Initialized As Boolean
Public Locations As Workbook
Sub Initialize()
If Initialized Then Exit Sub
Const fname As String = "M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx"
On Error Resume Next
Set Locations = Workbooks(Dir(fname))
On Error GoTo 0
If Locations Is Nothing Then
Set Locations = Workbooks.Open(fname)
End If
Initialized = True
End Sub
Then in the workbook's code module put:
Private Sub Workbook_Open()
Initialize
End Sub
Furthermore, in any "gateway" sub or function (e.g. event-handlers, UDFs, etc.) which might launch your code, put Initialize (or maybe: If Not Initialized Then Initialize) as the first line. Typically most subs won't be directly launched and can rely on Locations being properly set by the caller. If you need to test something which won't run properly if the variable isn't set then you can just type initialize directly in the Immediate Window.
You could also do it with a class module and rely on the class initialiser to do the work for you when it gets used in the module:
Class module called cLocations:
Public Workbook As Workbook
Private Sub Class_Initialize()
Set Workbook = Workbooks.Open("C:\Temp\temp.xlsx")
End Sub
And where you like in your module, or anywhere for that matter:
Dim Locations As New cLocations
Sub dosomething()
Locations.Workbook.Sheets(1).Cells(1, 1).Value = "Hello World"
End Sub
And then, you can just use Locations.Workbook to refer to the locations workbook, and ThisWorkbook to refer to the workbook the code is running in and ActiveWorkbook to refer to the workbook that has focus. This way you could run your code from one workbook (ThisWorkbook), using the locations workbook (Locations.Workbook) as a reference and iterate over other workbooks (ActiveWorkbook) to add another level of automation.
If you step through the code, you will see that the class is only initialised when you hit a line of code that requires it, not when the workbook is loaded.
I must add though, in this case I think if you give us a slightly bigger picture of what you are trying to achieve we might be able to give you a solution to a better problem than the one you have hit while coding.
You could also take this a step further, and abstract to the application level, keep the locations workbook hidden, and even provide intellisense for named sheets if you know their position or their name explicitly:
Class module:
Private App As Application
Public Workbook As Workbook
Public NamedSheet As Worksheet
Private Sub Class_Initialize()
Set App = New Application
App.Visible = False
App.DisplayAlerts = False
Set Workbook = App.Workbooks.Open("C:\Temp\temp.xlsx") 'maybe open read only too?
Set NamedSheet = Workbook.Sheets("SomethingIKnowTheNameOfExplicitly")
End Sub
Public Sub DoSomeWork()
'ThisWorkbook refers to the one the code is running in, not the one we opened in the initialise
ThisWorkbook.Sheets(1).Cells(1, 1).Value = Wb.Sheets(1).Cells(1, 1).Value
End Sub
Public Function GetSomeInfo() As String
GetSomeInfo = NamedSheet.Range("RangeIKnowTheNameOfExplicitly")
End Function
And then in your module, the first time you use the variable it will be initialised in one line of code:
Dim Locations As New cLocations
Dim SomeInfo
Sub DoSomething()
SomeInfo = Locations.GetSomeInfo 'Initialised here, other subs wont re-initialise
Locations.Workbook.Sheets(1).Cells(1, 1).Value = _
ThisWorkbook.Sheets(1).Cells(1, 1).Value
Locations.NamedSheet.Cells(1,1).Value = "Hello World!"
Locations.Workbook.Save
End Sub
This solution will work only if you know the numbers and names of all the worksheets that you will use from referenced workbook.
In your module, declare worksheet public variable for all your worksheets as follows:
Public sht1 As Worksheet
Public sht2 As Worksheet
Public sht3 As Worksheet
...
Instantiate these public variables in the application load event.
Sub Workbook_Open()
Workbooks.Open ("your referenced workbook")
'Instantiate the public variables
Set sht1 = Workbooks("Test.xlsm").Sheets("Sheet1")
Set sht2 = Workbooks("Test.xlsm").Sheets("Sheet2")
Set sht3 = Workbooks("Test.xlsm").Sheets("Sheet3")
End Sub
Now you can refer these global worksheets in your sub.
For example:
Sub test()
MsgBox sht1.Range("A1").Value
MsgBox sht2.Range("A1").Value
MsgBox sht3.Range("A1").Value
End Sub
If you create a Module say ExcelMod and within that Module you have a public function or subroutine Initialize() and another one called Terminate() you can initialize and terminate Module level variables using those routines. For example I have used this before: (Note that module variables are the first thing declared at the top of the module.)
Dim excelApp As Object, wb As Workbook, ws As Worksheet
Sub Initialize()
Set excelApp = CreateObject("Excel.Application")
Set wb = Workbooks.Open("C:\SomeOtherBook.xlsx")
End Sub
Sub Terminate()
Set excelApp = Nothing
Set wb = Nothing
End Sub
The variables are part of the entire module and only get initialized and terminated with these subroutines. You can pass the variables in and out of the module as you wish and use them in ALL of this modules subroutines without having to set again. If you need to use in another module you will need to pass it to that module as you normally would.
Also as others have mentioned you can use the workbook_Open event to call the initialization sub to create the objects and set them only once if needed.
Is this what you are after?
If I understand your question correctly, you are creating a code that should work on the application level and not on workbook level. In this case why don't you create an add-in.
All the code inside the add-in will have access to all the open workbooks at application level.
You might want to create an Add-In, or use a Class module to work with properties, ...
But I'm not sure it'll be that cleaner than a simple declaration in a regular module and a call to that procedure at workbook's open will do the trick just fine too .
(I have been using this method for quite some times and haven't been bothered)
So you can use this in a (dedicated or not) regular module :
'Set the path to your files
Public Const DESTBOOK = "M:\My Documents\MSC Thesis\Italy\Merged\DURUM IT yields merged.xlsm"
Public Const LOCBOOK = "M:\My Documents\MSC Thesis\Italy\Merged\locXws.xlsx"
'Declare all global and public variables
Global Locations As Workbook
Global MergeBook As Workbook
Global TotalRowsMerged As String
'Set all variable (Procedure call from Workbook_Open)
Sub Set_All_Global_Variables()
Set Locations = Set_Wbk(LOCBOOK)
Set MergeBook = Set_Wbk(DESTBOOK)
TotalRowsMerged = MergeBook.Worksheets("Sheet1").UsedRange.Rows.Count
'...
End Sub
'Function to check if the workbook is already open or not
Function Set_Wbk(ByVal Wbk_Path As String) As Workbook
On Error Resume Next
Set Set_Wbk = Workbooks(Dir(Wbk_Path))
On Error GoTo 0
If Set_Wbk Is Nothing Then
Set Set_Wbk = Workbooks.Open(Wbk_Path)
End If
End Function
And call the procedure setting all the variables in the ThisWorkbook module :
Private Sub Workbook_Open()
Set_All_Global_Variables
End Sub

Setting value of named range in Excel

I apologize if this is a total noob question. I'm in the process of writing an addin for Excel. I have a sub inside class1 that opens an excel file, in this sub I have a reference to sub2 which is below. All I am looking to do is hook into the active instance of Excel, change a named range value and exit. But I keep getting errors no matter which way that I try. Here is what I have. Tell me where I have gone wrong. Forgot to mention, this is in VB.NET.
Private Sub SetRangeValue(ByVal RangeName As String, ByVal RangeValue As String)
Dim ExcelApp As Excel.Application
Dim TheRange As Excel.Range
Dim TheRangeName As String = ""
'Hook into running excel instance
ExcelApp = CType(Marshal.GetActiveObject("Excel.Application"), Excel.Application)
'First Attempt Here
TheRange = ExcelApp.ActiveWorkbook.Names.Item(RangeName)
TheRange.Value = RangeValue
'Second Attempt
TheRange = ExcelApp.Range(RangeName)
TheRange.Value = RangeValue
End Sub
I can't get either one to work. Any help is appreciated.
Finally I got this to work properly. This is how it needed to work. Thanks for all the help.
Private Sub SetRangeValue(ByVal RangeName As String, ByVal RangeValue As String)
Dim ExcelApp As Excel.Application
'Dim TheRangeObj As Excel.Range
Dim TheRange As Microsoft.Office.Interop.Excel.Name
Dim TheRangeName As String = ""
'Hook into running excel instance
ExcelApp = CType(Marshal.GetActiveObject("Excel.Application"), Excel.Application)
TheRange = ExcelApp.ActiveWorkbook.Names.Item(RangeName)
TheRange.RefersToRange.Value = RangeValue
End Sub
'First Attempt Here
TheRange = ExcelApp.ActiveWorkbook.Names.Item(RangeName)
TheRange.Value = RangeValue
According to Names.Item Method (Excel), this function returns a single Name object from a Names collection. In this case TheRange is not valid name for this variable, it should be TheName. Then
TheName.Value = RangeValue
is not right assignment; According to Name.Value Property (Excel) this property - Returns or sets a String value that represents the formula that the name is defined to refer to.
Error 0x800A03EC, there are a number of reasons this error is returned from Excel - the most common is when attempts to write data larger than Excel can handle. For example, you try to write a string longer than 1024 characters to a cell in Excel
I just made a sub that would make it easier me to find & replace using my named ranges:
Private Sub XlFindReplace(ByRef xSheet As Excel.Worksheet, ByVal cellName As String, ByVal NewText As String)
xSheet.Range(cellName).Value = NewText
End Sub
then T'd call it like this in order to replace stuff:
XlFindReplace(xlC1Sheet, "client1Co1Tax", client1Co1Tax)
where the xlC1Sheet is the sheet I'm currently
the "client1Co1Tax"is the name of the range in excel
and client1Co1Tax is the string variable I'm replacing it
Thanks to everyone for their input.