Excel VBA to run after spreadsheet is refreshed - vba

I've read lots of other posts (particularly this) and even tried to replicate a few and I don't understand what's wrong.
I'm trying to get a function to run after the spreadsheet has been refreshed. It seems that I have to mess around with query tables, though if there is some way that I can avoid that, please tell me. I've attached what I think are the relevant code snippets (though I could have missed some).
The error I get is runtime eror: '9' Subscript out of range
code in "this workbook"
Dim qtevent As qtClass
Private Sub Workbook_Open()
Set qtevent = New qtClass
Set qtevent.HookedTable = ThisWorkbook.Worksheets("REFRESH SHEET").ListObjects(1).QueryTable
MsgBox "Qt Events Run"
End Sub
Code in class qtClass
Option Explicit
Private WithEvents qt As Excel.QueryTable
Public Property Set HookedTable(q As Excel.QueryTable)
Set qt = q
End Property
Private Sub qt_AfterRefresh(ByVal Success As Boolean)
MsgBox "qt_AfterRefresh called sucessfully."
If Success = True Then
MsgBox "If called succesfully."
End If
End Sub

Related

Print Button In Excel 2016 Using Macros?

I'm currently trying to create a Print Button on one of my worksheets. I need it to print that worksheet as well as another one. Both of the names are "Budget Sheet" and "Listed Commitments Sheet" without the quotation marks.
I created the button without hassle, but I know very little about Macros so I still need the code. I've tried multiple solutions, but nothing seems to work. I've recently tried to use this Code, but it hasn't worked. What am I doing wrong? What code could I possibly use instead?
Private Sub CommandButton1_Click()
Function PrintMultipleSheets()
Sheets(Array("Budget Sheet", "Listed Commitments Sheet")).PrintOut
End Function
End Sub
It comes up with an error that says "Compile Error: Expected End Sub".
The code doesn't compile, because you can't have a Function inside a Sub.
Get ride of the line Function PrintMultipleSheets() and get rid of End Function. It should work I think. You'll end up with:
Private Sub CommandButton1_Click()
Sheets(Array("Budget Sheet", "Listed Commitments Sheet")).PrintOut
End Sub
Just a simple loop with a print call:
Sub forEachWs()
Dim ws As Worksheet
For Each ws In ActiveWorkbook.Worksheets
Call printSheet(ws)
Next
End Sub
Function pasteContents(ws as Worksheet)
ActiveSheet.PrintOut
End Function

How do I enable Add-ins using VBA?

I'm trying to enable one of Excel's default add-ins with VBA, and it looks like I'm typing a boolean instead of a method. All of the information I find tells me that this should enable the the Solver Add-in.
AddIns("Solver Add-in").Installed = True
But it doesn't. Instead, it returns "Compile Error: Invalid outside procedure."
How do I enable the Solver Add-in when a worksheet opens?
Edit:
I'm running this code because I have to put methods inside subroutines. The problem is that it returns "Compile Error: Can't find project or library."
Option Base 1
Sub TurnOnSolver()
AddIns("Solver Add-in").Installed = True
End Sub
TurnOnSolver
Function cubic_spline(input_column As Range, _
output_column As Range, _
x As Range)
' The function does stuff that requires Solver Add-in.
End Function
You can't call a method outside of a sub or function, it has to be within a code block.
I'd suggest putting the call in the workbook open event; double click the workbook object in the project explorer and paste this code in:
Private Sub Workbook_Open()
AddIns("Solver Add-in").Installed = True
End Sub
Alternatively, if you have a main sub put it in there. As a last resort you could put it in your cubic_spline function but presumably that will be called quite often so I'd advise against it.

Excel VBA Run-time error 438 first time through code

I'm a novice self-taught VBA programmer knowing just enough augment Excel/Access files here and there. I have a mysterious 438 error that only popped up when a coworker made a copy of my workbook (Excel 2013 .xlsm) and e-mailed it to someone.
When the file is opened, I get a run time 438 error when setting a variable in a module to a ActiveX combobox on a sheet. If I hit end and rerun the Sub, it works without issue.
Module1:
Option Private Module
Option Explicit
Public EventsDisabled As Boolean
Public ListBox1Index As Integer
Public cMyListBox As MSForms.ListBox
Public cMyComboBox As MSForms.Combobox
Public WB As String
Sub InitVariables()
Stop '//for breaking the code on Excel open.
WB = ActiveWorkbook.Name
Set cMyListBox = Workbooks(WB).Worksheets("Equipment").Listbox1
Set cMyComboBox = Workbooks(WB).Worksheets("Equipment").Combobox1 '//438 here
End Sub
Sub PopulateListBox() '//Fills list box with data from data sheet + 1 blank
Dim y As Integer
If WB = "" Then InitVariables
ListBox1Index = cMyListBox.ListBoxIndex
With Workbooks(WB).Worksheets("Equipment-Data")
y = 3
Do While .Cells(y, 1).Value <> ""
y = y + 1
Loop
End With
Call DisableEvents
cMyListBox.ListFillRange = "'Equipment-Data'!A3:A" & y
cMyListBox.ListIndex = ListBox1Index
cMyListBox.Height = 549.75
Call EnableEvents
End Sub
...
PopulateListBox is called in the Worksheet_activate sub of the "Equipment" sheet.
All my code was in the "Equipment" sheet until I read that was bad form and moved it to Module1. That broke all my listbox and combobox code but based on the answer in this post I created the InitVariables Sub and got it working.
I initially called InitVariables once from Workbook_open but added the If WB="" check after WB lost its value once clicking around different workbooks that were open at the same time. I'm sure this stems from improper use of Private/Public/Global variables (I've tried understanding this with limited success) but I don't think this is related to the 438 error.
On startup (opening Excel file from Windows Explorer with no instances of Excel running), if I add a watch to cMyComboBox after the code breaks at "Stop" and then step through (F8), it sets cMyComboBox properly without error. Context of the watch does not seem to affect whether or not it prevents the error. If I just start stepping or comment out the Stop line then I get the 438 when it goes to set cMyComboBox.
If I add "On Error Resume Next" to the InitVariables then I don't error and the project "works" because InitVariables ends up getting called again before the cMyComboBox variable is needed and the sub always seems to work fine the second time. I'd rather avoid yet-another-hack in my code if I can.
Matt
Instead of On Error Resume Next, implement an actual handler - here this would be a "retry loop"; we prevent an infinite loop by capping the number of attempts:
Sub InitVariables()
Dim attempts As Long
On Error GoTo ErrHandler
DoEvents ' give Excel a shot at finishing whatever it's doing
Set cMyListBox = ActiveWorkbook.Worksheets("Equipment").Listbox1
Set cMyComboBox = ActiveWorkbook.Worksheets("Equipment").Combobox1
On Error GoTo 0
Exit Sub
ErrHandler:
If Err.Number = 438 And attempts < 10 Then
DoEvents
attempts = attempts + 1
Resume 'try the assignment again
Else
Err.Raise Err.Number 'otherwise rethrow the error
End If
End Sub
Resume resumes execution on the exact same instruction that caused the error.
Notice the DoEvents calls; this makes Excel resume doing whatever it was doing, e.g. loading ActiveX controls; it's possible the DoEvents alone fixes the problem and that the whole retry loop becomes moot, too... but better safe than sorry.
That said, I'd seriously consider another design that doesn't rely so heavily on what appears to be global variables and state.

VBA un-protect sheet, run sub, then re-protect sheet?

Here is my issue, I have subs that work when I tested them with the sheet unlocked, but when I locked the sheet to protect certain cells from being selected or deleted/altered, the subs error out. So I need to add a part to my sub that unlocks, runs the main code, then re-locks the sheet.
I am looking for something like this
Sub Example ()
Dim sample as range
set sample as range("A3:Z100")
Application.ScreenUpdating = false
UN-PROTECT CODE
'Existing sub code here
RE-PROTECT CODE
Application.ScreenUpdating = True
End Sub
I am however unaware on what the code to achieve this should look like. I have tried researching and all I found was incomplete code that based on the comments, didn't work all the time. I did find a suggestion to upon error, have an error handler re-protect the sheet, but not sure how to write this either. Any suggestions?
Oh, and the people who will be using this sheet will not have access to the sheet password. I plan to have the module its self password protected and the subs attached to buttons. So placing the Sheet unlock password in the sub would be ok if it is needed.
Posting my original comment as an answer.
If you use the macro recorder and then protect & unprotect sheets, it will show you the code.
EDIT: Added the below.
If you attempt to unprotect a sheet that is not protected you will get an error. I use this function to test if a sheet is protected, store the result in a Boolean variable and then test the variable to see if a) the sheet must be unprotected before writing to it and b) to see if the sheet should be protected at the end of the proc.
Public Function SheetIsProtected(sheetToCheck As Worksheet) As Boolean
SheetIsProtected = sheetToCheck.ProtectContents
End Function
Do you need it to remove passwords? This worked for me
Sub macroProtect1()
Sheet1.Unprotect Password:="abc"
'Enable error-handling routine for any run-time error
On Error GoTo ErrHandler
'this code will run irrespective of an error or Error Handler
Sheet1.Cells(1, 1) = UCase("hello")
'this code will give a run-time error, because of division by zero. The worksheet will remain unprotected in the absence of an Error Handler.
Sheet1.Cells(2, 1) = 5 / 0
'this code will not run, because on encountering the above error, you go directly to the Error Handler
Sheet1.Cells(3, 1) = Application.Max(24, 112, 66, 4)
Sheet1.Protect Password:="abc"
ErrHandler:
Sheet1.Protect Password:="abc"
End Sub
had a similar problem and found this code on the web:
Sub protectAll()
Dim myCount
Dim i
myCount = Application.Sheets.Count
Sheets(1).Select
For i = 1 To myCount
ActiveSheet.Protect "password", true, true
If i = myCount Then
End
End If
ActiveSheet.Next.Select
Next i
End Sub
Sub Unprotect1()
Dim myCount
Dim i
myCount = Application.Sheets.Count
Sheets(1).Select
For i = 1 To myCount
ActiveSheet.Unprotect "password"
If i = myCount Then
End
End If
ActiveSheet.Next.Select
Next i
End Sub
Note that it is designed to protect / unprotect all sheets in the workbook, and works fine. Apologies, and respect, to the original author, I can't remember where I found it (But I don't claim it)...
The most common object that is Protected is the Worksheet Object This make it possible to preserve formulas by Locking the cells that contain them.
Sub Demo()
Dim sh As Worksheet
Set sh = ActiveSheet
sh.Unprotect
' DO YOUR THING
sh.Protect
End Sub
Here's my very simple technique for situations that don't require a password (which are most situations that I run into):
Dim IsProtected As Boolean
IsProtected = SomeWkSh.ProtectContents: If IsProtected Then SomeWkSh.Unprotect
'Do stuff on unprotected sheet...
If IsProtected Then SomeWkSh.Protect
You can, of course, simplify the syntax a bit by using a With SomeWkSh statement but if the "Do stuff..." part refers to properties for methods of a larger, spanning With statement object, then doing so will break that functionality.
Note also that the Protect method's Contents parameter defaults to True, so you don't have to explicitly specify that, although you can for clarity.

Error calling macro from userform

I have a userform to call a macro in a separate module when a button is clicked. I get the following error: "Run-time error '450': Wrong number of arguments or invalid property assignment"
In troubleshooting I removed the arguments and changed the dummy macro I was calling to not take arguments, but I get the same error.
Public Sub btnSubmit_Click()
Dim Description As String
Dim Priority As String
If (checkCleared.Value = False) Then
MsgBox ("Please certify that all sensitive informationhas been removed and then submit")
Exit Sub
Else
'Description = formScreen.txtDesc.Value
'Priority = formScreen.comboPriority.Value
'Application.Run ThisOutlookSession!postScreenedEmail(Priority, Description)
Application.Run ThisOutlookSession!postScreenedEmail
End If
End Sub
In the separate module:
Public Sub postScreenedEmail() '(Priority As String, Description As String)
MsgBox ("postScreened")
'MsgBox ("Priority is: " & Priority & " and Description is " & Description)
End Sub
I have tried other methods of calling the macro such as "Call postScreenedEmail()" but it cant see the macro then. My end goal is just to grab values from the userform and pass them to the other macro so they can be used with the API I am working with.
Edit: I may have mixed my terminology, this is the hierarchy I am working with (can't post pic with my rep). That being said I tried to do the call with just Application.Run "postScreenedEmail", Priority, Description and it changed nothing
-Project1(VbaProject.OTM)
-Microsoft Outlook Objects
| ThisOutlookSession
-Forms
| formScreen
|
-Modules
Module1
Try:
call postScreenedEmail
instead of:
Application.Run ThisOutlookSession!postScreenedEmail
Since your sub is public, vba should be able to find it without the module reference.
If this works, add the reference again (makes your code more readable, especially for others, as ckuhn203 pointed out in the comments) and see if it breaks. If so, that's where the problem is.
EDIT:
Are you sure you're referencing the right module?
If I try:
-Project1(VbaProject.OTM)
-Microsoft Outlook Objects
| ThisOutlookSession
-Modules
| Module1
in Module1:
Sub jzz()
Debug.Print "test"
End Sub
and in ThisOutlookSession:
Sub test()
Call Module1.jzz
End Sub
it works. No problem. Using:
Application.Run Module1.jzz
instead of Call trows a compile error.
Even:
Sub test2()
Call ThisOutlookSession.test
End Sub
from Module1 works, without problems.
Can you run such small tests to try to get the references right?
Try this... Application.Run takes a string for procedure name, and then comma-separated list of parameters/arguments:
Application.Run "Procedure_Name", arg1, arg2, arg3
So I think this should work:
Application.Run "ThisOutlookSession!postScreenedEmail", Priority, Description