In my workbook I have 3 SQL-database queries which are triggered using a
'Initiate datbase querying
ThisWorkbook.RefreshAll
In my DB_Connection worksheet I added the following code to verify if the query ran successfully or failed (to be used in a log-sheet later on). If there are no more queries running, the macro continues with the next phase.
Private Sub QueryTable_AfterRefresh(Success As Boolean)
Dim Succeeded As Integer
Dim Failed As Integer
Succeeded = 0
Failed = 0
If Success Then
Succeeded = Succeeded + 1
Worksheets("DB_Connection").Range("L2").Value = Succeeded
Else
Failed = Failed + 1
Worksheets("DB_Connection").Range("M2").Value = Failed
End If
End Sub
However, the QueryTable_AfterRefresh is never called. I placed a stop to identify if it is being called or not.
Any suggestions?
You can't just type out an event handler signature and expect it to work. If you navigate to that QueryTable_AfterRefresh procedure, you should notice the contents of the code pane drop-downs - the only way a handler procedure named QueryTable_AfterRefresh could exist and work, is if you have a Private WithEvents QueryTable As QueryTable declaration:
Notice the left-hand dropdown says QueryTable (the name of the WithEvents field) and the right-hand dropdown says AfterRefresh (the name of the event).
If what you have is this (General) on the left and QueryTable_AfterRefresh on the right:
...then what you're looking at is essentially dead code that nothing will ever invoke, at least not through QueryTable events.
Declare a module-level WithEvents variable, select it from the left-hand dropdown, then select the AfterRefresh event in the right-hand dropdown; the VBE will generate the correct method signature for that event on that object.
Then you need to Set that object reference before you do ThisWorkbook.RefreshAll. You could do that in the Workbook_Open handler, however with the field being Private you won't be able to access it from the ThisWorkbook module. One solution is to make it Public, a better solution is to expose a method to properly initialize it:
Option Explicit
Private WithEvents QueryTable As QueryTable
Public Sub Initialize()
Set QueryTable = Me.QueryTables(1)
End Sub
Private Sub QueryTable_AfterRefresh(ByVal Success As Boolean)
'...
End Sub
And then give that worksheet a compile-time code name (set its (Name) property in the properties toolwindow) and call that method from Workbook_Open in ThisWorkbook - for example if the sheet has QuerySheet for a code name, you can invoke it like this (note there's no need to dereference that object from the ThisWorkbook.Worksheets collection):
Option Explicit
Private Sub Workbook_Open()
QuerySheet.Initialize
End Sub
Related
I am generating a scripting dictionary using one button on a userform, using it to populate a listbox, and then need to use that same dictionary using a second button on the form. I have declared my dictionary either using early binding as so:
Dim ISINDict As New Scripting.Dictionary
or late binding as so
Dim ISINDict as Object
...
Set ISINDict = CreateObject("Scripting.Dictionary")
When I try to pass the dictionary to the other button like so:
Private Sub OKButton_Click(ISINDict as Scripting.Dictionary) 'if early binding
Private Sub OKButton_Click(ISINDict as Object) 'if late binding
I get the following error: "Procedure declaration does not match description of event or procedure having the same name" on that line.
Any ideas what I'm doing wrong?
An event handler has a specific signature, owned by a specific interface: you can't change the signature, otherwise the member won't match the interface-defined signature and that won't compile - as you've observed.
Why is that?
Say you have a CommandButton class, which handles native Win32 messages and dispatches them - might look something like this:
Public Event Click()
Private Sub HandleNativeWin32Click()
RaiseEvent Click
End Sub
Now somewhere else in the code, you want to use that class and handle its Click event:
Private WithEvents MyButton As CommandButton
Private Sub MyButton_Click()
'button was clicked
End Sub
Notice the handler method is named [EventSource]_[EventName] - that's something hard-wired in VBA, and you can't change that. And if you try to make an interface with public members that have underscores in their names, you'll run into problems. That's why everything is PascalCase (without underscores) no matter where you look in the standard libraries.
So the compiler knows you're handling the MyButton.Click event, because there's a method named MyButton_Click. Then it looks at the parameters - if there's a mismatch, something is wrong: that parameter isn't on the interface, so how is the event provider going to supply that parameter?. So it throws a compile-time error, telling you you need to either make the signature match, or rename the procedure so that it doesn't look like it's handling MyButton.Click anymore.
When you drop a control onto a form, you're basically getting a Public WithEvents Button1 As CommandButton module-level variable, for free: that's how you can use Button1 in your code to refer to that specific button, and also how its Click handler procedure is named Button1_Click. Note that if you rename the button but not the handler, the procedure will no longer handle the button's Click event. You can use Rubberduck's refactor/rename tool on the form designer to correctly rename a control without breaking the code.
Variables in VBA can be in one of three scopes: global, module, or procedure level.
When you do:
Sub DoSomething()
Dim foo
End Sub
You're declaring a local-scope variable.
Every module has a declarations section at the top, where you can declare module-scope variables (and other things).
Option Explicit
Private foo
Sub DoSomething()
End Sub
Here foo is a module-scope variable: every single procedure in that module can access it - read and write.
So if you have data you want to pass between procedures and you can't alter their signatures, your next best option is to declare a module-scope variable.
[ignores global scope on purpose]
About As New - consider this:
Public Sub Test()
Dim foo As Collection
Set foo = New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code blows up with run-time error 91 "object variable not set", because when foo.Add executes, foo's reference is Nothing, which means there's no valid object pointer to work with. Now consider this:
Public Sub Test()
Dim foo As New Collection
Set foo = Nothing
foo.Add 42
Debug.Print foo.Count
End Sub
This code outputs 1, because As New keeps the object alive in a weird, unintuitive and confusing way. Avoid As New where possible.
Declare the dictionary at the module level and fill it in button-1-click event handler. Then it can be simply re-used in button-2-click event handler. So there is no need to pass the dictionary to event handlers which is not possible either. HTH
Form module
Option Explicit
' Declare dictionary at the user form module level
Private ISINDict As Scripting.Dictionary
Private Sub CommandButton1_Click()
FillDictionary
End Sub
Private Sub CommandButton2_Click()
' Use the dictionary filled in event handler of CommandButton-1
End Sub
Private Sub FillDictionary()
With ISINDict
.Add "Key-1", "Itm-1"
.Add "Key-2", "Itm-2"
.Add "Key-3", "Itm-3"
End With
End Sub
Private Sub UserForm_Initialize()
Set ISINDict = New Scripting.Dictionary
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
I'm new to VBA and am trying to write a macro that will execute a field update before the user saves the document. My first attempt was going to be intercepting the Save command like this:
Sub FileSave()
'
' FileSave Macro
' Saves the active document or template, and updates fields
'
Fields.Update
ActiveDocument.Save
End Sub
But one guide recommended not doing that and instead using DocumentBeforeSave(), so this is my new attempt:
Private Sub oApp_DocumentBeforeSave(ByVal Doc As Document, _
SaveAsUI As Boolean, Cancel As Boolean)
Fields.Update
End Sub
The first example gives me the following error:
"Runtime error '424': Object required"
The second example doesn't update the fields. I've tried the second code in both the ThisDocument object, and as a new module class. Nevertheless, the fields are still not updating when I save. On a side note, they work with this.
Private Sub Document_Close()
Fields.Update
Save
End Sub
It seems like a simple task but it just doesn't seem to work.
In your ThisDocument code module, add an Application object using the WithEvents keyword so that you can respond to its events:
Dim WithEvents TheApp As Word.Application
Add an event handler for Document_Open() that assigns your variable to the active Application object:
Private Sub Document_Open()
Set TheApp = ThisDocument.Application
End Sub
Add an event handler for Application_DocumentBeforeSave(). Within this event use the Doc object that's passed to it to update your document:
Private Sub TheApp_DocumentBeforeSave(ByVal Doc As Document, SaveAsUI As Boolean, Cancel As Boolean)
Doc.Fields.Update
End Sub
Save your document as a "Macro-Enabled Document (*.docm)".
Close the document and reopen it.
That should be it. If you want to make sure your code is working properly, add a breakpoint or a MsgBox() before your Doc.Fields.Update statement to make sure the event handler is getting called and check your values before and after the update.
I have a Worksheet_BeforeDoubleClick event that checks to see whether a cell that's clicked has data that's in a Dictionary object, like so:
Private Sub Worksheet_BeforeDoubleClick(ByVal Target as Range, Cancel as Boolean)
Dim dict as Dictionary
Dim df as New dictFactory
'returns a dictionary populated with all list items
Set dict=df.create
If dict.Exists(Target.Value) Then
MsgBox "Exists"
Else
MsgBox "Doesn't exist"
End If
End Sub
The problem is that this requires creating a new dictionary each time a cell is clicked. I thought it would be good to store the dictionary in a global variable in its own module, like this:
Global valuesDict As New Dictionary
and then populate it upon opening the workbook:
Private Sub workbook_open()
Dim df as New dictFactory
Set valuesDict=df.create
End Sub
But I've encountered a lot of problems with this during testing, because there are many conditions under which a global variable's value can be reset (as discussed here).
How can I store an object so that its value will be available as long as the workbook is open, throughout repeated calls to my BeforeDoubleClick event?
Global valuesDict As Dictionary 'EDIT - drop the "new"
Private Sub Worksheet_BeforeDoubleClick(ByVal Target as Range, Cancel as Boolean)
'populate global only when needed
if valuesDict is Nothing then CreateDict
If dict.Exists(Target.Value) Then MsgBox "Exists"
Else
MsgBox "Doesn't exist"
End If
End Sub
'
Private Sub workbook_open()
CreateDict
End Sub
'
Sub CreateDict()
Dim df as New dictFactory
Set valuesDict=df.create
End sub
It's true that data of a module level variable (a.k.a. global variable) persists until the workbook is closed but an incomplete execution of code (due to a bug or deliberate interruption) will reset the variable, wiping out that data. It happens to static variables as well which work like module level variables in terms of duration even though static variables are local in scope.
To be safe, you can write code in the worksheet module to check if the global variable (referencing a dictionary) is valid, if not, run a dedicated procedure to recreate a dictionary.
BTW, the dictionary object has no Create method.
I have a userform with a basic combobox and command button. When the user hits the command button, I want the UserForm to close, and the value of the combobox to be saved in a variable that can be accessed by a subroutine contained within "ThisWorkbook".
In the UserForm code:
Public employee_position As String
Public Sub CommandButton1_Click()
employee_position = Me.ComboBox1.Value
Unload Me
End Sub
In the "ThisWorkbook" Code
Private Sub GetUserFormValue()
Call Userform_Initialize
EmployeePosition.Show
MsgBox employee_position
End Sub
When "GetUserFormValue()" runs, the UserForm comes up, you can select a value in the combobox and press the command button, but when the MsgBox comes up, it displays "" (Nothing)
What am I doing wrong here?
When you Unload Me, I think you lose all information associated with the module (including the global variable). But if you use Me.Hide rather than Me.Unload, then you can access the value of the form after the routine returns. So try this:
-- userform code includes:
Public Sub CommandButton1_Click()
Me.Hide
End Sub
-- main module includes:
Private Sub GetUserFormValue()
Call Userform_Initialize
EmployeePosition.Show
MsgBox EmployeePosition.ComboBox1.Value
Unload EmployeePosition
End Sub
I think that should work.
I had the same problem, and this is how I resolved it:
If the main code is in a worksheet, and the variable is declared as public in that worksheet (e.g. in Microsoft Excel Objects -> Sheet1 (Sheet1)), the result from "Unload Me" cannot be passed from a UserForm to the worksheet code.
So to solve my problem, I inserted a new Module, and declared my public variable there. I didn't even have to move my code from the worksheet to the module... just the declaration of the public variable.
I hope this works for you too!
Andrew