I have a workbook that contains a Macro that checks the workbook out from our SharePoint server, updates it, and then should check it back in. Afterwards, it should remain open for the user review and possibly execute other updates. The main problem I have is that the ".checkin" method closes out the Workbook, terminating the macro before it can re-open the workbook. I had found a proposed solution here: https://stackoverflow.com/a/22380886/8858822 , however I have encountered a problem with how Application.OnTime operates.
If I attempt to use the Application.Workbook.Open procedure as an argument for the Application.OnTime method, as suggested by the link above, the .Open procedure gets called immediately, without waiting the desired delay. In this scenario, the workbook has not been checked-in and closed yet, causing an error:
Sub TestSub()
Dim wb As Workbook
Set wb = ThisWorkbook
Dim FPath As String
FPath = wb.FullName
Workbooks.CheckOut (FPath)
'''''''''''''''''''''''''
'execute workbook updates
'''''''''''''''''''''''''
Application.DisplayAlerts = False
On Error Resume Next
Application.OnTime Now + TimeValue("00:00:10"), Application.Workbooks.Open(FPath)
wb.CheckIn
Application.DisplayAlerts = True
End Sub
Is there a reason that Application.Workbooks.Open(FPath) executes immediately, instead of waiting the specified 10 seconds? If I can delay that execution by 10 seconds, the code would execute correctly.
I noticed that if instead of directly opening the file, I call one the macros in the file, it does wait the desired 10 seconds. This allows for the file to first be checked in and closed, then re-opens it and executes the specified macro.
Sub TestSub()
Dim wb As Workbook
Set wb = ThisWorkbook
Dim FPath As String
FPath = wb.FullName
Workbooks.CheckOut (FPath)
'''''''''''''''''''''''''
'execute workbook updates
'''''''''''''''''''''''''
Application.DisplayAlerts = False
On Error Resume Next
Application.OnTime Now + TimeValue("00:00:10"), " ' file address & name '!OtherMacroName "
wb.CheckIn
Application.DisplayAlerts = True
End Sub
With this code, the file is checked in, closes, and then after 10 seconds reopens the file and runs the macro. Unfortunately, due to trust center settings I believe, I cannot directly call macros from workbooks saved on our SharePoint server, and this code generates an permissions error. If I relocate the file to my personal or our network drive, it executes without error. I have not been able to resolve the SharePoint permissions issue, even after following: https://stackoverflow.com/a/21175812/8858822 .
If I can understand why the Application.Workbooks.Open procedure operates immediately, while the macro call procedure waits the desired 10 seconds, when each is an argument used by Application.OnTime, I believe the code would work as desired. If there is a way to call the macro within the closed workbook after 10 seconds, I presume there must be some way of just opening the workbook after the 10 seconds? Thank you for your time and assistance.
OnTime takes the name of a subroutine as its argument. You need to create a new routine for OnTime to call (it doesn't actually have to do anything if all you need is the workbook reopened).
Is there a reason that Application.Workbooks.Open(FPath) executes
immediately, instead of waiting the specified 10 seconds? If I can
delay that execution by 10 seconds, the code would execute correctly.
Yes. The answer you linked is incorrect.
Application.OnTime Now + TimeValue("00:00:10"), SecondArgument
The SecondArgument is the name of a subroutine to call.
What you're doing when you put Application.Workbooks.Open(FPath) in SecondArgument is opening the workbook, then returning a reference to the workbook, in the same way you would go:
Set wb = Application.Workbooks.Open(FPath)
Put differently, when you do this...
Application.OnTime Now + TimeValue("00:00:10"), Application.Workbooks.Open(FPath)
... you're actually doing this:
Set wb = Application.Workbooks.Open(FPath)
Application.OnTime Now + TimeValue("00:00:10"), wb
So that answers your question, but to solve your problem...
From this link, there's some sort of dirty workaround where the key is apparently to call the Application.OnTime method a moment before you close the workbook (check in the workbook in your case):
Sub CloseMe()
Application.OnTime Now + TimeValue("00:00:02"), "OpenMe"
ThisWorkbook.Close ' check in
End Sub
Sub OpenMe()
' literally does nothing
End Sub
So Excel knows to run the OpenMe method in 2 seconds' time, and it is that act of attempting to run the OpenMe method that actually re-opens the workbook.
You can test this by having two workbooks:
E:\Documents\Book3.xlsm has the following code:
Sub TestMe()
MsgBox "test"
End Sub
E:\Documents\Book2.xlsm has the following code:
Sub OpenAnotherWorkbook()
Application.Run "'E:\Documents\Book3.xlsm'!TestMe"
End Sub
Making sure that E:\Documents\Book3.xlsm is currently closed, we run OpenAnotherWorkbook from E:\Documents\Book2.xlsm.
It will open E:\Documents\Book3.xlsm and run TestMe.
Related
I use Excel 2016. The file I am using runs some code in workbook Open event handler.
Depending on what the code does the workbook may never get opened (visible).
in Workbook_Open the event handler establishes a connection with a data base and load some data eventually warn the user if an error occurs for instance.
Establishing the connection with a data base and load some data works most of the time. Some times it does not and the Excel splash screen remains on the screen for ever. After closing the splash screen clicking on the close cross, the Excel process continues running in the background.
If an error occurs I don't see the pop up form which is opened to warn the user as long as the workbook is not visible.
when I kill the process an reopen Excel (not the workbook), Excel suggest to retrieve the file which was opened earlier proving somehow that it has in deed been opened.
The second process just does not work unless the workbook is visible. If I run Excel and then open the workbook it s ok (in case an error occurs the warning pop up form is opened).
If Excel is closed and i open the workbook it does not work (splash screen frozen).
what is the proper way to start a VBA application where It is necessary to manage some cosmetics tasks (show some sheets, hide some other) and run a connection to a data base?
Here is a simplified version of the code (can't provide it all - too complex - too long)
In this code the function bConnectToTheDatabase is forced to return False which make the bLogin function useless (the code is not provided for this reason).
Private mLoginForm As frmLgn ' a form to login the application (see image)
Private mCustomMsgBox As frmMsgBox ' a form to display custom messages (see image)
Public Const gsPFU_VBA_PWD as String = "my secret password"
Public Const gsBLANK_WKS_NAME As String = "Blank"
Private Sub Workbook_Open()
'
Call HidShowSomeSheets()
' start the application up
Call LoginIntoTheApplication()
End Sub
' hide all sheets but the one named gsBLANK_WKS_NAME
' protect/unprotect to chnage visibility
Sub HidShowSomeSheets()
With Application.Workbooks(ThisWorkbook.Name())
.Unprotect (gsPFU_VBA_PWD)
.Sheets(gsBLANK_WKS_NAME).Visible = xlSheetVisible
For Each wks In .Sheets
With wks
.Unprotect (gsPFU_VBA_PWD)
End With
Next wks
For Each wks In .Sheets
With wks
If (StrComp(gsBLANK_WKS_NAME, .Name) <> 0) Then
.Unprotect (gsPFU_VBA_PWD)
On Error Resume Next
.Visible = xlSheetVeryHidden
Else
.Protect (gsPFU_VBA_PWD)
.Activate
End If
End With
Next wks
.Protect (gsPFU_VBA_PWD)
End With
End Sub
Sub LoginIntoTheApplication()
if( bConnectToTheDatabase() ) then
call bLogin
else
'create a custom form (it has it own even handler - block the execution flow)
'set mCustomMsgBox= new frmMsgBox
'mCustomMsgBox.Show vbModeless 'this does not work
MsgBox "something went wrong" 'this works
end if
end Sub
Function bLogin() as Boolean
'login into the application
End Function
Function bConnectToTheDatabase() as Boolean
'do the connection
'get the data
'etc ....
bConnectToTheDatabase= False 'fore it to False for the purpose of the explanation
End Function
The login form
The custom message box
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.
Hi Iam new to writing VBA code and need assistance. My VBA code and Macro works fine as long as I am on the active sheet.
My Problem:
My VBA code and macro, stops running automatically, when i change from the active sheet to another within the same workbook and
My VBA code and macro, stops running automatically, when i open a new excel workbook
Solution required:
Run the VBA code and macro only on desired worksheet and prevent it from running on other worksheets and workbooks.
Background:
I have an excel file, named "Net Weight" with two sheets. sheet 1 is named : "weight", sheet 2: is named "base data".
sheet 1 is used as a user input form.
In sheet 1- cell B1 : user will type in a product code , in cell E1: a look up formula will place the description of the product code using the data from sheet 2
I have setup a VBA code and macro that does the following:
As soon as a user inputs a product code into cell B1, in sheet 1, sheet 1 is saved as PDF file into a predefined folder location data from cell B1 and E1
A macro saves and overwrties the PDF file every 10 seconds.
This process is repeated every time a new product code is entered
There are no buttons on sheet 1, everything is done automatically.
Here is my current code:
Sheet 1: set as Worksheet
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "$B$1" Then
Call Macro1
End If
End Sub
Module 1 macro : set as general
Sub Macro1()
Application.DisplayAlerts = False
If ThisWorkbook.Name = "Nett Weight.xlsm" And ActiveSheet.Name = "Weight" Then
Sheets("Weight").ExportAsFixedFormat Type:=xlTypePDF, _
Filename:="C:\Nett weight\" & Range("B1 ").Text & Range(" E1").Text
Application.OnTime Now + TimeValue("00:00:10"), "Macro1"
Else
Exit Sub
End If
End Sub
First, you need to create a public variable to hold your timer, otherwise you'll never be able to cancel it so it will continue trying to fire even when your workbook is closed. You should also create a public variable to store when the timer is running, so you can check before creating a new timer.
At the top of a code module put:
Public nextTime As Date
Then in your Workbook_BeforeClose() event method (within ThisWorkbook), disable the existing timer so it doesn't keep trying to fire after the workbook is closed.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
On Error Resume Next ' Continue with next line of code if we encounter an error
Application.OnTime Earliesttime:=nextTime, Procedure:="Macro1", Schedule:=False
On Error GoTo 0 ' Resume error-trapping
End Sub
In Macro1() you should explicitly and directly reference your workbook components - ThisWorkbook always refers to the workbook the code is running from, so you don't need your If statement. Then you set the nextTime and activate the timer using that variable if it is not already running.
Sub Macro1()
Dim sht As Worksheet ' Creates a variable to hold your Weight worksheet
Set sht = ThisWorkbook.Sheets("Weight") ' Sets the reference
Application.DisplayAlerts = False
sht.ExportAsFixedFormat Type:=xlTypePDF, Filename:="C:\Nett weight\" & sht.Range("B1").Text & sht.Range("E1").Text ' Remember to preceed Range with sht. to explicitly reference the range of your Weight worksheet
On Error Resume Next ' Continue with next line of code if we encounter an error
Application.OnTime Earliesttime:=nextTime, Procedure:="Macro1", Schedule:=False
On Error GoTo 0 ' Resume error-trapping
nextTime = Now + TimeSerial(0, 0, 10) ' Adds 10 seconds to Now
Application.OnTime Earliesttime:=nextTime, Procedure:="Macro1", Schedule:=True
timerIsRunning = True
Application.DisplayAlerts = True ' Remember to enable alerts at the end of code
End Sub
Your Worksheet_Change() event method can stay as is. Now if there is a change in B1 it will call Macro1(). Macro1() will save the Weight worksheet as a PDF regardless of whether the workbook or worksheet is active, and create a new timer to re-run Macro1() every 10 seconds after deactivating an existing timer. When you're finished with the workbook, you close it and the existing timer is also deactivated.
EDIT:
Fortunately (as it fixes a spreadsheet of my own) I have figured out why the solution I originally provided wasn't working. Placing the Public variables under ThisWorkbook meant they no longer held their values after code execution. The remedy was to place them in a module instead. Once that was sorted out, I also realised that when the timer fires to call Macro1() it will throw an error when trying to unschedule the existing timer (as none exists unless Macro1() was called ad hoc by the Worksheet_Change() event).
Long story short: Public variables have been moved to a code module, and the timerIsRunning flag has been removed entirely and errors when attempting to unschedule the timer are simply ignored in the event that no timer exists.
I created an Add-In that adds a sheet to a Workbook and imports multiple modules. I would then like for the Add-in to run a Subroutine that is now in Active Workbook. This is what I have so far, and I'm getting Run-time error '438': Object doesn't support this property or method.
What is the correct syntax (if it can even be done). Thanks.
' Class name is EventClassModule
Public WithEvents App As Application
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
If Wb.Name = "Just To Test.xls" Then
Wb.Sheets.Add Type:="C:\TestGLPage.xls"
fname = Dir("C:\Users\Me\Desktop\BAS\*.*", vbNormal)
While fname <> ""
If Right(fname, 3) = "frm" Or Right(fname, 3) = "bas" Or Right(fname, 3) = "cls" Then
ActiveWorkbook.VBProject.VBComponents.Import "C:\Users\Me\Desktop\BAS\" & fname
End If
fname = Dir() 'get the next file
Wend
Call Application.Workbooks("Just To Test.xls").starter
End If
End Sub
VBA is not a dynamic language. When you alter the code or the names of code objects at runtime VBA has to recompile those modules before the changes can be accessed. You may find breakpoints don't work properly after you've made such changes as well.
This recompilation happens automatically and immediately, but is not accessible from code that is already executing. You need to get Excel to re-enter the VBA code.
You may be able to get away with using Application.Run, but I would probably use Application.OnTime to be safer if you don't need starter to be a blocking call (which appears to be the case in your example code).
Just for clarity, when calling a subroutine using Application.Run or Application.OnTime you cannot qualify it using the module name. You can however qualify it using the workbook's name using bang syntax. Eg. Application.Run "Book1.xlsx!SubNameToBeCalled"
Example
I created a blank workbook
I created two modules
Imported
Public Sub RunMe()
MsgBox "Test!"
End Sub
Main
Public Sub Run()
ActiveWorkbook.VBProject.VBComponents.Import "C:\Temp\Imported.bas"
' Showing how to do it with Run
Application.Run ThisWorkbook.Name & "!RunMe"
' Showing the safer way with OnTime
Application.OnTime Now, ThisWorkbook.Name & "!RunMe"
End Sub
I then exported the "Imported" module to "C:\Temp\Imported.bas" and removed it from the project.
i have created macro for excel but it seems somewhere i have done something wrong,
i want to fetch an image from a URL and then update it up to 1 second (more or less)
Sub GetPicture()
PictureURL = "This is where i put the URLi want"
Set MyPict = ActiveSheet.Pictures.Insert(PictureURL)
Cells(1).Value = Now
nextTime = Now + TimeValue("00:00:01")
End Sub
when i run the macro doesn't do anything,only when i press f5 the it updates as fast as i press f5,also what is the value to update less than 1 second ("00:00:01"),when i try ("00:00:0.5") it comes up with "run time error 13" "type mismatch"
Any help is very much apreciated.
In Excel, you can use VBA to trigger code that updates a Worksheet on specific intervals. The code below shows how you would activate a Timer each time the Worksheet is activated by a user. Whenever the Timer fires (on 1 second intervals here) this code updates Cell A1 in the ActiveSheet with the current Time.
To further customize, you would add code to the OnTimerMacro in order to update a Picture or whatever else you recurring task might be.
(Props to Hartmut Gierke for his post on the topic.)
Option Explicit
Dim Execute_TimerDrivenMacro As Boolean
Sub Start_OnTimerMacro()
Execute_TimerDrivenMacro = True
Application.OnTime Time + TimeValue("00:00:01"), ActiveSheet.Name & ".OnTimerMacro"
End Sub
Sub Stop_OnTimerMacro()
Execute_TimerDrivenMacro = False
End Sub
Public Sub OnTimerMacro()
If Execute_TimerDrivenMacro Then
' Do something e.g. put the actual time into cell A1 of the active sheet
ActiveSheet.Cells(1, 1).Value = Time
' At the end restart timer
Application.OnTime Time + TimeValue("00:00:01"), ActiveSheet.Name & ".OnTimerMacro"
End If
End Sub
Private Sub Worksheet_Activate()
'Start the timer driven method when opening the sheet
Start_OnTimerMacro
End Sub
Private Sub Worksheet_Deactivate()
'Stop the timer driven method when opening the sheet
Stop_OnTimerMacro
End Sub
if you would like the macro to repeat you have to put it in a do...until loop. The only problem, is that you can't really have the macro run all the time. There has to be a way to stop it. The do...until loop will help with this, but you have to come up with a reasonable exit from the loop. Can you give a little more background as to what you ultimately want this to do?
Also it sounds like you want the running of the macro to be triggered by something other than the pressing of F5. Can you explain when you would like to see it start?