I think I'm misunderstanding something simple here, as usual, some fundamental principle I have yet to learn.
Anyway, I am trying to set up a combobox for the purpose of selecting from the active workbooks and passing the workbook name to a variable for use in future code.
However, I could only get this to work by setting said variable type as Variant, and after the code finishes executing I am still getting an error... so what's going on there then?
Private Sub cmdPopulate_Click()
If cmbWorkbooks.Value <> "" Then
selectedWorkbook = cmbWorkbooks.Value
Call populateChecklist
End If
Unload Me
End Sub
Private Sub UserForm_Initialize()
For Each wb In Workbooks
cmbWorkbooks.AddItem wb.Name
Next wb
Me.Show
End Sub
Public wb As Workbook
Public ws As Worksheet
Public selectedWorkbook As Variant
Sub populateChecklist()
MsgBox "hello " & selectedWorkbook
End Sub
Edit:
To clarify the error I receive upon finished execution is:
Run-time error '91':
Object variable or With block variable not set
Edit2:
ok I think I've figured it out...
I commented out everything by degrees until I found the culprit.
It seems to be caused by Me.Show in the userform_Initialize sub, which is apparently redundant code anyway!
Related
I have a workbook that has global variables declared:
Public var1 As Long
Public var2 As Long
Public var3 As Long
...
Public varN As Long
I also have a workbook open sub that assigns values to global variables when a workbook is opened:
Private Sub Workbook_open()
Application.Calculation = xlCalculationAutomatic
Call calculateRows
Call assignVariables
ThisWorkbook.Worksheets("Filling").Protect "somepass", UserInterfaceOnly:=True
MsgBox ("DONE Workbook_open")
End Sub
It works fine. However, I also have a worksheet selection change sub:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
...
End Sub
and worksheet change sub:
Private Sub Worksheet_Change(ByVal Target As Range)
...
End Sub
which both contain code that can end up with an error. Whatever error occurs, it always erases the value of my global variables, which only leads to further errors and basically a complete shutdown of the workbook since all the worksheet selection/change subs heavily use those global variables.
What I should do to ensure it wouldn't happen? Would using an 'On Error GoTo' operation be sufficient? Why would global variables get erased anyway?
As mentioned in the comments, clicking End to halt the execution will always reset the global state. Heck, there need not be an error; clicking the Stop button will have the same effect.
You should have error handling at least within the entry procedures (e.g. your worksheet event handlers are good candidates since they are the entry point where Excel can interact with your VBA code).
If you want to be able to find the offending error even with a error handler, this pattern can help:
Public Sub Example()
On Error GoTo ErrHandler
'Some code that might error
ExitProcedure:
On Error Resume Next
'Clean up
Exit Sub
ErrorHandler:
MsgBox "Oops"
Resume ExitProcedure
Resume 'for debugging
End Sub
Note the unreachable Resume at the end of the error handler. It's unreachable because Resume ExitProcedure will always execute first. So when you get the messagebox, you can use ctrl+break to break into code, which will then take you to the Resume ExitProcedure. You can then drag the yellow arrow over to the unreachable Resume, press F8 to step once which will then take you back to the line that caused the error.
Note that it only works for in procedure; if error was bubbled, it will return you to the procedure that was called before the error bubbled but not the line within the procedure.
Or, if you don't like all extra work, you can consider buying an addin such as vbWatchDog which helps significantly with the error handling.
Even so, those do not help with the root problem -- the global state is fragile and can be destroyed... there's the Stop button or even End available for you. You want to avoid using global as much as possible and scope them or stuff them in a class instance so that when you repeat a code block, it can rebuild the state within that scope. The less things you have in the global state, the better.
I'm trying to create a macro (in PERSONAL.XLSB) that every time a workbook is opened, it checks a condition and in if it's true (this means the workbook opened contains an specific Sub), it calls this Sub.
Option Explicit
Private WithEvents App As Application
Private Sub Workbook_Open()
Set App = Application
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
If condition Then Call Specific_Sub
End Sub
It runs fine when I open a file that contains that Sub, however, if the Sub is not in the file, the compiler returns the error “Sub or Function not defined”, naturally.
I’m trying very hard to find a way to do this and deal with the error, but On error GoTo doesn’t work because the compiler error is before the run time, so it’s not executed.
I guess I have to do this in a different way but I can’t picture how to do it, any help or ideas?
Thanks a lot!
Thanks to the answers I've discovered that the best way is to use Application.Run. To keep the code as simple as possible, I just changed the last part to look like this:
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
On Error Resume Next
If condition Then
Application.Run ("'" & ActiveWorkbook.FullName & "'!" & "Specific_Sub")
End If
End Sub
Thank you all.
I cobbled this together from a few web sites. The key is that your sub routines name is in a variable and application. run uses the variable. This gets past the compiler error you are running into
Sub SubExists()
Dim ByModule As Object
Dim ByModuleName As String
Dim BySub As String
Dim ByLine As Long
'
'Module and sub names
ByModuleName = "Module1"
BySub = "Specific_Sub"
On Error Resume Next
Set ByModule = ActiveWorkbook.VBProject.vbComponents(ByModuleName).CodeModule
ByLine = ByModule.ProcStartLine(BySub, vbext_pk_Proc)
If Err.Number = 0 Then
Application.Run BySub
End If
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
SubExists
End Sub
I have the following code to activate a macro when a change is made to cell A1
Class Module
Option Explicit
Private WithEvents App As Application
Private Sub Class_Initialize()
Set App = Application
End Sub
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Sh.Name = "S" Then
Dim rngKeyCells As Range
Set rngKeyCells = Sh.Range("A1")
If Intersect(rngKeyCells, Target) Is Nothing Then
Exit Sub
End If
Application.Run "a"
End If
End Sub
This_Workbook Code
Private OurEventHandler As EventHandler
Private Sub Workbook_Open()
'Initiates the data change when the filter is changed
Set OurEventHandler = New EventHandler
End Sub
This works absolutely fine usually, however an issue occurs if i try making a change in A1 after i open VBA.
It will work fine 90% of the time but if during one of the previous macro's that i run, there is an error, it won't work.
Example - I run a macro that deletes the Worksheet to the left of the active one. If there is no worksheet to the left of the active one it will error. I press end and that's fine. Now if i try to change the cells A1 and expect the macro above to run, nothing happens.
Is this the kind of thing that is solvable without showing the entire macro? Or could it be something that is inbuilt into the rest of the macro that is causing the issue?
Thanks
In the programming there is something named Design Patterns. In your case, it would be really useful to make a Singleton for the App variable.
Here is a good example of it for VBA:
How to create common/shared instance in vba
As already mentioned in the comments: When an error happens and end is pressed, the whole VBA-Context is reset, the content of global Vars is lost (so your variable OurEventHandler is nothing).
If you can't catch all errors to ensure that this reset isn't happening (and I think you never really can), maybe it is easiest to implement the event handler in the worksheet itself:
Private Sub Worksheet_Change(ByVal Target As Range)
' Ne need to check Worksheet as the Hander is valid only for the Worksheet
if target.row = 1 and target.column = 1 then
Application.Run "AIMS_and_eFEAS_Report.AIMS_Criteria"
end if
End Sub
Bit of a newbie to VBA, sorry
I need to create some variables that are available throughout my workbook, but I can't seem to figure it out. I've read in previous questions where some people have suggested create a separate dim for this?
When the workbook opens I need to set some variables equal to certain cells in a worksheet, these variables need to be called from dims in others worksheets.
So far I have tried to use
Workbook_Open()
In the 'ThisWorkbook' code area but to no avail.
Any tips?
Reagards
EDIT ----
I have tried with the following:
In 'ThisWorkbook'
Public wsDrawings As String
Public Sub Workbook_Open()
wsDrawings = "Hello"
End Sub
And in Sheet1
Private Sub CommandButton1_Click()
MsgBox wsDrawings
End Sub
I do not get an error, but the message box is empty.
Just declare the variables you need wherever they are first used (ThisWorkbook is a fine place to do it) and replace the typical Dim with Public. It will then be accessable in all your code
You can create global variable with code like this
Public testVar As String
you need to place it outside function or sub and then this variable has value till you close workbook. But i think it have scope only in current module.
So you can have something like this
Public testVar As String
Private Sub Workbook_Open()
testVar = "test"
End Sub
Sub testEcho()
MsgBox testVar
End Sub
for shared variable between multiple modules look here
edit:
So now i found, that you can use public variable from ThisWorkbook using this
Sub testSub()
MsgBox ThisWorkbook.wsDrawings
End Sub
you can use module for creating global variable.
First my sub looked like this:
Sub DeleteGraph()
For Each objCht In Me.ChartObjects
For Each s In objCht.Chart.SeriesCollection
s.Delete
Next s
Next objCht
End Sub
This didn't work when called from the main code because of Me.
Then I tried this:
Sub Main()
Dim test As Worksheet
Set test = Me.CodeName
DeleteGraph test
End sub
Sub DeleteGraph(sheets As Worksheet)
For Each objCht In sheets.ChartObjects
For Each s In objCht.Chart.SeriesCollection
s.Delete
Next s
Next objCht
End Sub
This didn't work of the "Set test = Me.CodeName"
I searched around the web and found out that I might have to use 'CallByName' but I can't seem to make it work. Is this the right approach and if so, how?
Thank you!
In VBA Me is context sensitive. It provides a reference to the parent object. If your code is declared within ThisWorkbook Me will point to the current workbook object. If you declare your code within a userform Me will refer to that userform.
I suspect your code was originally declared within a Sheet object.
From here it would access to the current worksheet object, which contains the ChartObjects object.
One way to fix your code is to move it into the required worksheet. You could also parameterise the workbook name, like so:
Sub DeleteGraph(ByVal WorksheetName AS String)
For Each objCht In Sheets(WorksheetName).ChartObjects
For Each s In objCht.Chart.SeriesCollection
s.Delete
Next s
Next objCht
End Sub