I'm looking for a bit of advice here.
I have a spreadsheet with various modules and procedures that can be called from a Worksheet_Change event. This causes problems when I need to issue a sheet from the workbook for other users to complete.
Whenever the user tries to update the sheet, the on change event gets triggered, causing a compile error as the procedure being called does not exist, and this cannot be trapped (as far as I'm aware). I've tried using Application.EnableEvents = False, but this is in the worksheet event and the code breaks as soon as the event is triggered.
Is there anyway to call a procedure through late binding where I can trap the error?
I'm trying something like this at the moment.
Dim mdl as object
' Test for module in workbook, if error, then exit routine
Set mdl = Application.ActiveWorkbook.VBProject.VBComponents("mdlSharedFunctions")
'If no error, then call procedure here
call mdl.UpdateData(Target)
'Or
Application.Run mdl.UpdateData(Target)
Neither of these call methods will work and I'm hoping someone out there will be able to point me in the right direction.
Cheers
Pete
You can use a global variable as a flag - bit dirty but it works fine. Then add an If flag = true then statement to the change event sub.
Public globalflag as Boolean
Sub test1()
If globalflag = True Then
BrokenSub 'This sub has an invalid sub/function referenced, but will be ignored if the flag is set to false
Else
'Don't run the code
Exit Sub
End If
End Sub
Sub BrokenSub()
invalidfunction ("asb")
End Sub
EDIT
To put it in a worksheet, just see if the variable exists:
Declare this in a module in your master spreadsheet:
Public globalflag as Boolean
Then in your worksheet code
If not IsEmpty(globalflag) Then
BrokenSub 'Put your master spreadsheet code here - it'll run if globalflag exists and be ignored if it doesn't
End If
Sub BrokenSub()
invalidfunction ("asb")
End Sub
Related
Let me preface by saying I am a self taught novice at VBA or coding for that matter.
I have a combobox "cmbStyle" not in a userform but diretly on a worksheet named "Cost". I need to run code on the change event of "cmbStyle". The problem I have is that when the user changes the combobox the event fires and the code runs but at the end of the code the combobox event fires again and so on and so on.
I know that Application.EnableEvents = False will have not effect on ActiveX controls so this is not a solution.
I have found descriptions on how to use a boolean variable to stop the looping in the change event for listboxes but I can't seem to get it to work in my instance.
My code will end the subroutine after the Cange Event fires the second time. However, the next time the user selects another value from the Combobox the CodeDoneRun variable is still TRUE so the subroutines won't run again when I need it to.
I feel I am missing something very basic here....
My code is as follows:
Public CodeDoneRun as Boolean
Private Sub cmbStyle_Change()
If CodeDoneRun = True Then Exit Sub
CodeDoneRun = True
Call other Subroutines
End Sub
Enclose your function call in the if statement and let the code run through.
Public CodeDoneRun as Boolean
Private Sub cmbStyle_Change()
If Not CodeDoneRun Then
Call other Subroutines
End If
CodeDoneRun = Not CodeDoneRun
End Sub
I have 50 datasheets in the project, and nobody remembers to run the save macro when going to another sheet. The bright idea is to use a private sub Worksheet_Deactivate to do the necessary calculations when they select another worksheet. In addition to the 50 datasheets, there are two more worksheets in the workbook for which the calculation must not run. It would be nice if the sub could be put in "Worksheets" rather than replicated 50 times in individual worksheets, but the two other worksheets need to be excluded from processing.
Problem is, the sub defaults to the deactivating worksheet (such as an unqualified "Range.Value =" in the macro code), but the active worksheet is now the worksheet being navigated TO. So any ActiveXXXXX statement directs to the wrong worksheet. Worksheet.Name is disallowed.
Datasheets are numbered 1 to 50. What is needed is a statement early in the deactivate sub similar to
If DeactivatingWorksheet(X) = "BasicInfo" Or "Constants" Then GoTo EndSub
where X is the value of the deactivating worksheet. Of course, X is known only to Excel at the moment of processing.
I can't seem to figure out how to refer to the deactivating worksheet in the macro's IF statement. Any ideas?
Use Workbook_SheetDeactivate(ByVal sh as Object) instead of Worksheet_Deactivate(). The Workbook-level event supplies the name of the sheet being departed, even though in both cases the ActiveSheet has already changed when when event fires. Use sh just like a worksheet variable - sh.Name, sh.ProtectionMode, etc.
Now you don't need 50 subs; just one. Another thing that this allows is, you can "abort" the change to the now ActiveSheet by sh.Activate to the old one (but turn off events or you'll have a lovely infinite loop).
Me also gives the old sheetname and works for the worksheet event, if you still want to go that way. Me is the old one, ActiveSheet is the new one.
If you are using Worksheet_Deactivate and this calls a subroutine in a seperate module, you can pass the name of the deactivating worksheet to the subroutine.
For instance, if your subroutine is something like:
Sub test()
ActiveSheet.Range("whatever") = "something"
ThisWorkbook.Save
End Sub
And you call it from the worksheet like:
Private Sub Worksheet_Deactivate()
Module1.test()
End Sub
You can add a parameter to the subroutine to take the worksheet name, and add a test:
Sub test(worksheetname as string)
If worksheetname <> "dontsavethistab" then
ActiveSheet.Range("whatever") = "something"
'or... you could also do:
Sheets(worksheetName).Range("Whatever") = "something"
ThisWorkbook.Save
End If
End Sub
And call it from your Worksheet_Deactivate event like:
Private Sub Worksheet_Deactivate()
Module1.test (Me.Name)
End Sub
If you wanted to get a little cleaner, instead of the worksheet name you could pass the worksheet object:
Private Sub Worksheet_Deactivate()
Module1.test(Me)
End Sub
Sub test(ws as worksheet)
If ws.name <> "dontsavethistab" then
ws.Range("Whatever") = "something"
ThisWorkbook.Save
End If
End Sub
This way you have the entire worksheet object to do with as you please in your subroutine.
I have an excel workbook.
In one of the worksheets I make use of the Worksheet_Calculate event. This works fine. However when the workbook is first opened I do not want MyFunction to be called. What is the best away to do this?
My only idea so far is the following (I don't like this though). On the workbook open method put a time stamp in one of the worksheets and then have an if statement in my worksheet_calculate and if the current time is 1 minute past the time stamp (that was created on the workbook_open event) run the code otherwise don't.
Thinking there must be a better way though?
Private Sub Worksheet_Calculate()
MyFunction()
End Sub
Update
The reason I do not want my code to execute when the workbook opens is because there are some Bloomberg formulas that take a little time to execute so initially some of the cell values are #NA.
This causes a type mismatch error - any errors that happen are logged and an e-mail is automatically sent. So every time the workbook is opened there is an 'error' as the bloomberg formulas have not updated straight away
The code below should work for you.
The first time calculate is called it sets the flag to allow future calls to process. This means the first call to calculate does not process your code.
ThisWorkbook Code:
Private Sub Workbook_Open()
bFunctionFlag = False
End Sub
Sheet Code:
Public bFunctionFlag As Boolean
Private Sub Worksheet_Calculate()
If bFunctionFlag = True Then Call MyFunction
bFunctionFlag = True
End Sub
Private Function MyFunction()
MsgBox "Calculate"
End Function
In the workbook open event you can have
Private Sub Workbook_Open()
Application.EnableEvents = False
'Run Workbook Open code
Application.EnableEvents = True
End Sub
or if you need events to run from external services then do something similar with a public variable. Eg Dim wbOpenEventEnded as Boolean
I have a workbook with multiple Worksheet_Change events in the sheet.
Each of these macroes are related to an active x combobox.
The problem is that when I change one of the comboboxes the macro fires (as expected), which in turn start another macro (which is not suppose to run). I have already set the Application.EnableEvents = False
But the issue might be that I am changing a cell.value, which is linked to another combobox and hence also linked to another worksheet_change event.
A workaround as I see it, might be to only run the macro, if the combobox is the one actually selected, but here comes the second problem. I can't find a way to have vba return the name of the active combobox.
Please note that these comboboxes is not connected to a userform, they are simply placed directly on the worksheet.
Is there anybody who has any idea on how to solve this??
Any help is much appreciated,
I see that there are 2 possible solutions...
1 - as stated by Gary's Student, you may have toggled the Application.EnableEvents somewhere unknowingly and you could try and trap where that happens.
2 - Maybe set a global boolean variable called, say DontRunMacros and set it to TRUE at the start of the first macro and to FALSE at the end. Then you simply have each other macro start with If DontRunMacros Then Exit Sub - That will prevent the others running regardless of the events that fire...
Hope that helps!
You could also set a global variable handling events. You check it at the beginning of each change event.
Dim ufEventsDisabled As Boolean
Sub YourSub()
ufEventsDisabled = False
Range("A1").Value=1 'This triggers the event
ufEventsDisabled = True
Range("A1").Value=1 'This doesn't trigger the event
End Sub
Private Sub Worksheet_Change(ByVal Target As Range)
If ufEventsDisabled=True Then Goto ExitEvent:
'Your regular worksheet code
ExitEvent:
ufEventsDisabled=False
End Sub
There is probably an error somewhere that is re-Enabling Events...........You can always insert:
MsgBox Application.EnableEvents
at points in your code to trap this.
Thanks a lot for the quick responses.
I ended up using a named cell in the worksheet, similar to what hstay sugested.
If ThisWorkbook.Sheets("MD").Range("AllowMacro").Value = 0 Or Me.Name <> ActiveSheet.Name Then Exit Sub
However as I need the worksheet to calculate some cells while the code is running, I can't set application.Calculation = xlManual.
So the code still tries to execute a bunch of other change events, but the code above stops them from running more than just the first line. This however still takes a lot of time, which is quite frustrating. Guess I'll just need to take this in to account another time.
This is how I begin and end all my worksheet_chnage events:
If ThisWorkbook.Sheets("MD").Range("AllowMacro").Value = 0 Or Me.Name <>
ActiveSheet.Name Then Exit Sub
ThisWorkbook.Sheets("MD").Range("AllowMacro").Value = 0
Application.ScreenUpdating = False
Application.EnableEvents = False
'some random code that trigger other change_events
Application.EnableEvents = True
Application.ScreenUpdating = True
ThisWorkbook.Sheets("MD").Range("AllowMacro").Value = 1
this one may be impossible to solve in VBA but I'd like to see what you experts have to say about it.
I have a textbox on a userform that triggers a macro within a TextBox1_Change() type of sub.
If the user types "ABC" in the textbox, the macro gets triggered 3 times: once for "A", once for "AB" and once for "ABC". This macro is actually kind of heavy, so I would like it to run only when the user is actually done typing, and not inbetween single key strokes.
I know I can make the user "press enter" or whatever and only then run the macro, but this is not what I'm looking for. I want him to type freely and see the results of his typing dynamically show up, with no other type of interaction required.
So, I came up with the idea of making the change event wait and see if another change event gets triggered within, say, 1 second from the first. If that happens, the first change event aborts.
Now this would work, and I think I would know how to code it, except that I don't know how to give the user the power to keep typing even when the first change event is running.
What I mean is that when the first macro runs, it "freezes" everything. Waiting to see if another change event triggers will therefore not work, as nothing is going to trigger until the first macro is done running.
Do you guys see my problem here? How would you go about this? Any chance I can achieve the results I'd like?
Any help is greatly appreciated, thanks guys!
I tested the following, and it works (assuming I correctly understand what you're trying to do).
In a code module, write this:
Public aRunIsScheduled As Boolean
Public nextRunTime As Variant
Sub MyMacro()
'Flag macro as having been run, no longer scheduled.
aRunIsScheduled = False
'Place your macro code here.
'I'll just use some dummy code:
MsgBox "MyMacro is running!"
End Sub
In your sheet module:
Private Sub CommandButton1_Click()
If aRunIsScheduled Then
' Cancel the previously scheduled run.
Application.OnTime EarliestTime:=nextRunTime, _
Procedure:="MyMacro", Schedule:=False
aRunIsScheduled = False
End If
' Schedule a new run 3 seconds from now:
nextRunTime = Now + TimeValue("00:00:03")
Application.OnTime EarliestTime:=nextRunTime, _
Procedure:="MyMacro", Schedule:=True
aRunIsScheduled = True
End Sub
I put a Commandbutton in my sheet and here I'm using its change event, but you can put this code in your TextBox1_Change() event instead, in exactly the same way.
reference: http://www.cpearson.com/excel/SuppressChangeInForms.htm
To suppress events in a form, you can create a variable at the form's module level called "EnableEvents" and set that to False before changing a property that will cause an event to be raised.
Public EnableEvents As Boolean
Private Sub UserForm_Initialize()
Me.EnableEvents = True
End Sub
Sub Something()
Me.EnableEvents = False
' some code that would cause an event to run
Me.EnableEvents = True
End Sub
Then, all of the controls on form should have a test if that variable as their order of business in any event code. For example,
Private Sub ListBox1_Change()
If Me.EnableEvents = False Then
Exit Sub
End If
MsgBox "List Box Change"
End Sub
You can declare the EnableEvents as Private if only procedures with that form need to suppress events. However, if you have forms that are programmatically linked together, such UserForm2 adding an item to a ListBox on UserForm1, you should declare the variable as Public and set it for another form with code like the following:
UserForm1.EnableEvents = False
'
' change something on UserForm1
'
UserForm1.EnableEvents = True
The primary difference between the EnableEvents property and code shown above and the Application.EnableEvents property is that with a UserForm EnableEvents, all control on the form must have code to exit if EnableEvents is True. In other words, all the form's controls must cooperate and respect the setting of EnableEvents.