I have an Excel workbook containing some ComboBox controls placed directly on the sheets. These are standard combo boxes from the Forms toolbar.
When the user uses "Save As" to save the workbook with a different name, this triggers the Change event on all the combo boxes, including ones on sheets that aren't active. This seems unreasonable as the selection hasn't actually changed. This causes various undesirable behaviour because of the code in the event handlers. The event isn't triggered on a simple "Save".
Google suggests this is a known problem in Excel. There are rumours that it's caused by using a named range as the ListFillRange for the combo box, which I have done, although it's not a volatile name. I'm looking for a way to prevent this happening with minimal changes to the code and the spreadsheet. Does anyone have a proven solution?
I did the following in a new workbook with only one sheet, Sheet1, and it seemed to work to diable events before save and then reenable them after. It should bypass the problem you see by mimicing an AfterSave event. This is my event code on Sheet1 (could be OLEObject code, too)
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
MsgBox Target.Address & ": " & Target.Value
End Sub
This is my ThisWorkbook code
Option Explicit
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
' To see the change code work before disabling
' Should show a message box
Sheet1.Range("A1") = "Before After Events Off"
Application.EnableEvents = False
Application.OnTime Now, "ThisWorkbook.Workbook_AfterSave"
' This time it will not show a message box
' You will never see this one . . .
Sheet1.Range("A1") = "After After Events Off"
End Sub
Private Sub Workbook_AfterSave()
Application.EnableEvents = True
End Sub
The .OnTime method throws the AfterSave "event" onto the execution queue. It works!
You could set a flag in the Workbook's BeforeSave event and then check that flag before processing a change event in each of the combo boxes. There does not seem to be an AfterSave event, so you would need to clear the flag after checking it within the combo box change events. The flag would need to be more than a simple boolean since it could not be turned off until all combo box change events were processed. Here's some sample code:
Public ComboBoxChangeCounter As Integer
Private Sub Workbook_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
Const NumOfComboBoxChangeEvents As Integer = 5
ComboBoxChangeCounter = NumOfComboBoxChangeEvents
End Sub
Function JustSaved() As Boolean
If ComboBoxChangeCounter > 0 Then
ComboBoxChangeCounter = ComboBoxChangeCounter - 1
JustSaved = True
End If
End Function
Private Sub Combo1_Change()
If JustSaved Then Exit Sub
'Your existing code '
' ... '
' ... '
' ... '
End Sub
I set the number of combo box change events as a constant, but there may be some way for you to determine that number programmatically. This workaround does require adding code to every combo box change event, but it should be easy as all you need to do is copy and paste the line If JustSaved Then Exit Sub at the beginning of each event.
This workaround assumes that the Workbook BeforeSave event will get called prior to the combo box change events. I don't know for a fact if that's the case.
Related
When user click TAB key from keyboard, some cells should be updated in the same row(Event should be happen iff there is some data in that cell). As of know I written some sample code to display some message. I written code inside the workbook as below
Private Sub Workbook_SheetselectionChange (ByVal Sh As Object, ByVal Target As Range)
Application.EnableEvents = True
Application.OnKey"{TAB}", "sayHi"
End Sub
Sub sayHi()
Application.EnableEvents = True
MsgBox "Hiiii"
End Sub
Issue :- whenever I am clicking TAB key, getting one warning like "cannot run the macro 'C:...sayHi'. The macro may not be available in this workbook or all macros may be disabled".
I have a Workbook with multiple Sheets. I have a menu page (Worksheet) with multiple user choices (Enter a new order, update an order, etc.) Each choice has a check box beside it and depending on which check box is checked, cells F4:F21 change from 0 to 1 and, cell B1 changes to the name of the Worksheet where I want to go. I have the following VBA in the Main Menu worksheet but when I click a check box, nothing happens. Any ideas why?
CODE
Private Sub Worksheet_Activate()
ClearMenuForm
End Sub
Private Sub Worksheet_Change (ByVal Target As Range)
Dim sh As String
If Not Intersect(Target, Range("F4:F21")) Is Nothing Then
sh = Cells(1, "B").Value
Sheets(sh).Select
End If
End Sub
Clicking a check box does not activate the event Worksheet_Change (see this). That is why nothing happens.
Try changing one of the cells instead to see the effect.
What I think you want to do is assign an action to your Checkbox(es). You can do this in two ways:
Right clicking on the checkbox, and Assign Macro...
You have to create the associated macro, which will likely contain parts of the code that you already wrote, and/or calls to subs you have. You may bring the VBE (Alt+F11), insert a module in your VBA project, and write your Sub, e.g.,
Sub CheckBox1_Click()
MsgBox "Checkbox 1a has changed"
End Sub
Via VBA (e.g., this). With the sample code below, you would execute InitCBs, and that would associate CheckBox1Change with the checkbox (it actually assigns actions for both checkboxes in the figure; action for checkbox 2 is CheckBox2Change). You may also set InitCBs to be executed when opening the file.
Sub CheckBox1Change()
MsgBox "Checkbox 1b has changed"
End Sub
Sub InitCBs()
Dim cb As CheckBox
For Each cb In ActiveSheet.CheckBoxes
With cb
Dim action As String
'action = "CheckboxChange"
action = Replace(cb.Name, " ", "") & "Change"
.OnAction = action
End With
Next cb
End Sub
You've defined sh as a String. Since there is no sheet named "1", for example, your code will generate a "Subscript out of Range" runtime error. Try changing sh to a Long.
Dim sh As Long
I have a macro which is basically working as I want (alerting the user when two conflicting checkboxes are selected) - code below. The 1 in G2 is the value generated to indicate this case.
The error message fires on SelectionChange, but this appears to be only when another cell is selected by mouse. The worksheet contains a series of checkboxes for the user to select from, and the intention is for the user to only use the checkboxes, never needing to select or input directly into cells. In which case, the error message would never fire even when the scenario described has occurred.
Is there a way of having a msgbox macro trigger by the update of any checkbox on the sheet?
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Range("G2") = 1 Then
MsgBox "ERROR - Select AND Reject checked"
End If
End Sub
Also, I would like to extend the range to apply to all the cells in column G, I just can't seem to get this to work for me. I have seen a few examples citing "G:G" but I have so far only got this to work for one cell.
Apologies in advance for any glaring errors, I've used Excel for a while now - but I'm brand new to using VBA.
Mutually exclusive options are usually indicated with option buttons (also known as radio buttons) instead of checkboxes. Is there any reason you're not using option buttons for this task?
As far as calling the same code for all checkboxes, the checkboxes would have to be Form Controls (not ActiveX Controls), and you could assign them to this macro:
Sub CheckBox_Clicked()
Dim chk As CheckBox
Set chk = ActiveSheet.CheckBoxes(Application.Caller)
MsgBox chk.Name
End Sub
And lastly, for your SelectionChange event to monitor an entire column, it would look similar to this:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim ClickedCell As Range
Dim rngClicked As Range
Application.EnableEvents = False
Set rngClicked = Intersect(Columns("G"), Target)
If Not rngClicked Is Nothing Then
For Each ClickedCell In rngClicked.Cells
If ClickedCell.Value = 1 Then MsgBox "ERROR - Select AND Reject checked"
Next ClickedCell
End If
Application.EnableEvents = True
End Sub
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.
I don't seem to understand the strange behaviour of Excel 2007, and after a dozen solutions, I came to you asking for help.
I have office 2007.
Here is my class module code :
Public WithEvents App As Application
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
MsgBox "event din app"
End Sub
Here is my InitializeAppObject module :
Dim X As New EventClassModule
Sub InitializeApp()
Set X.App = Application
MsgBox "am facut setarea"
End Sub
Here is my Sheet one code :
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
If Not Application.Intersect(Target, Me.Range("a1:c10")) Is Nothing _
Then
Application.EnableEvents = False
MsgBox "suntem in range"
Application.EnableEvents = True
Else
MsgBox "nu suntem in range"
End If
End Sub
Before changing any values on the grid, I execute the InitializeApp() procedure.
From my understanding, the sheet event should be triggered first, then the workbook one, and then the application one. However this is not the case. It first fires up the workbook one, the the application one, and finally the sheet one.
Is Excel malfunctioning or I got it wrong ?
How can I execute just the sheet event? cause obviously the Application.EnableEvents = false is not doing any good from stopping the event from being triggered upstream.
Thank you so much in advance for all your help!
kind regards,
radu
I think the problem may be that you're using the SelectionChange event in the sheet module. You're correct about the order of events. Here's how it goes - you're events have a *
Worksheet_Change
Workbook_SheetChange*
Application_SheetChange*
then assuming the selection moves after you enter something
Worksheet_SelectionChange*
Workbook_SheetSelectionChange
Application_SheetSelectionChange
Nothing in your code is triggering events, so putting message boxes in between EnableEvents doesn't achieve the desired result. All of the events are already queued up by the time the first event code runs. If instead, you had
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Application.EnableEvents = False
Sh.Range("a1").Value = 1
Application.EnableEvents = True
End Sub
Then the code that changed the value of A1 would not trigger any events. If you only want to run the Worksheet_Change event, you should delete the other event code. OK, it's probably there for a good reason. But whatever the logic is for when you run which event code needs to be in the procedure. For instance, if you only want to run the Worksheet_Change event on a particular worksheet name "Master", you would set it up like this
Private Sub App_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If Sh.Name <> "Master" Then
MsgBox "event din app"
End If
End Sub
And the Worksheet_Change event code would be in master sheet's class module, so it would only respond to events on that page.