I'm writing a simple macro to solve a common problem for people who use XLS with multiple monitors. Often, a workbook is saved in the second window and will not appear in the main window when re-opened.
To get around this, I have attempted to use the App_WorkbookOpen function and have encountered several problems.
This code only runs when opening a fresh instance of XLS.
This code opens the workbook, but does not display it in the windows menu.
This code interacts only with the personal.xls template.
I've included several lines of code to test what the macro is interacting with.
The code resides in ThisWorkbook section of my personal template.
The core code works if placed in an individual worbook, but I'd like it to run in all Wb_open situations.
Option Explicit
Public WithEvents App As Application
Private Sub Workbook_Open()
Set App = Application
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
On Error Resume Next 'error handling, remove when final
Application.ScreenUpdating = False
If ActiveWindow.Left > Application.UsableWidth Then
ActiveWindow.Left = 15
ActiveWindow.Top = 10
End If
MsgBox "New Workbook:" & Wb.Name 'what wkbook?, remove when final
If Error Then End
End Sub
Any thoughts?
Related
My application currently navigates to a vendor website, logs in, opens a report, clicks a download button behind an IFrame(which was cancer to get working), and hits the open button on the open/save dialog box. Now all thats left for this phase of the project is to have excel wait for the new workbook to open, pull the information into a new sheet, and close the other workbook.
The problem is that no matter what I seem to try the new workbook will never open while the macro is running. If i add a break in the code everything works just fine. I've tried application.wait, sleep, DoEvents, and even a msgbox. I'm kinda stumped here.
'This clicks the button that initiates the open/save dialog box in IE
iWin.contentDocument.querySelector("a[title=Excel][onclick*=EXCELOPENXML]").Click
'set the name of the windows the program should look for
iStr = "Reports LeaveXpert Application - Internet Explorer"
eStr = "Case Detail.xlsx [Protected View] - Excel"
'these are declared as longptr
leHwin = 0
cdHwin = 0
'Grabs the windows handle for the IE window
leHwin = FindWindow(vbNullString, iStr)
'Makes sure the IE window is active
i = SetForegroundWindow(leHwin)
'a little bit of delay
Application.Wait DateAdd("s", 5, Now)
'Send the key combination for the open button to the open browser
Application.SendKeys "%{O}"
cdHwin = 0
Debug.Print cdHwin
'this should wait for the new spreadsheet to open but instead the application hangs here and the new spreadsheet never opens.
Do While cdHwin = 0
'various methods of having the program wait that i've tried
'DoEvents
'Application.Wait (Now() + TimeValue("00:00:01"))
'Sleep 1000
'this should set the variable to the windows handle of the new sheet once it opens causing the loop to stop
cdHwin = FindWindow(vbNullString, eStr)
'This just keeps giving me 0 as an output
Debug.Print cdHwin
Loop
'we never get to this line of code unless I add a break somewhere in the loop, then the sheet opens and I get the windows handle as output.
Debug.Print cdHwin
UPDATE:
IT FINALLY WORKS!!!!!!!!!!!!!!!
I'll have to rethink my single button approach to implementation, but that's a pretty small price to pay. This has been an off and on project for the better part of 2 weeks and has taken me far more time than I anticipated, mostly due to IFrames being absolute cancer (I seriously hate them). But I've learned a lot getting this first part working. Now to start on getting the other two relevant data sources integrated into the spreadsheet. The more I learn about computer programming; the more I realize how little I know about it.
Oh and Mathieu-Guindon is an absolute hero!
UPDATE 2 for posterity.
Nothing to see here. I thought I had resolved the problem, but as it turns out I just need more sleep. Relevant quote from comment discussion.
"I really need to stop staying up till 4 AM reading posts about computer code and trying to work on this thing. I really think that I went to sleep, had a dream about fixing it, woke up and posted about the "fix", and thought it was real."
Update 3: Final Update on this question. Relevant explanation is in the comments on the following code.
'I made 2 changes that seem to have resolved the issue.
Private Sub AppEvents_WorkbookOpen(ByVal Wb As Workbook)
If Left(Wb.Name, 11) = CaseDetailFileName Then
'This sub that handles the transfer of data from the newly opened workbook to this workbook.
'It had an internal call to close the newly opened workbook that I forgot about, so I removed it
ImportCase Wb
'a duplicate call also existed out here, there were also a few duplicate ".activate"
'commands inside the import sub and this if statement that I removed
Wb.Close
'This sub handles data sorting and analytics
CaseFAS
End If
'I had tried copying this line from Workbook_Open a few times before and it
'didn't resolve the issue by itself,
'but I'm reasonably sure it is part of the solution
Set AppEvents = Me.Application
End Sub
A peek under the hood of ImportCase for completeness sake.
'This sub handles importing the optis workbook when it opens. It's called by
'the AppEvents_WorkbookOpen sub declared in ThisWorkbook
Public Sub ImportCase(ByVal book As Workbook)
Dim srcSht As Worksheet
Set srcSht = ThisWorkbook.Sheets("OptisSource")
'This line clears out any old data
srcSht.Cells.Clear
'Sets the newly opened workbook to be the active sheet
book.Sheets("Case Detail").Activate
'Copys the information and sends it to a predefined sheet in this workbook
ActiveSheet.UsedRange.Copy Destination:=srcSht.Range("A1")
'sets the newly imported information to the active sheet
srcSht.Activate
'This sub clears out ghost data
ClearNulls
End Sub
What if we just let Excel do its thing, and go with the flow of an event-driven / desynchronized paradigm?
The Excel.Application class raises a number of events you can handle in any class module, but the ThisWorkbook module can do - you declare a WithEvents module-level variable:
Option Explicit
Private WithEvents AppEvents As Excel.Application
And then you initialize it with an Application object reference, perhaps on open - if we're in the ThisWorkbook module, we're inheriting an Application property from the base Excel.Workbook class:
Option Explicit
Private WithEvents AppEvents As Excel.Application
Private Sub Workbook_Open()
Set AppEvents = Me.Application
End Sub
Declaring a WithEvents variable makes it available in the top-left dropdown, and then we can pick a member to implement with the top-right dropdown:
One of the events we can handle at the Application level, is a rather convenient WorkbookOpen event that gives us a Wb As Workbook reference to the workbook that was just opened:
Private Sub AppEvents_WorkbookOpen(ByVal Wb As Workbook)
End Sub
Here you get to intercept every workbook that opens during a macro-enabled session, as long as this AppEvents reference remains in-scope - that is, until ThisWorkbook is closed.
So the ThisWorkbook might look like this:
Option Explicit
Private Const CaseDetailFileName As String = "Case Detail.xlsx"
Private WithEvents AppEvents As Excel.Application
Private Sub Workbook_Open()
Set AppEvents = Me.Application
End Sub
Private Sub AppEvents_WorkbookOpen(ByVal Wb As Workbook)
If Wb.Name = CaseDetailFileName Then
DoThingWithCaseDetailWorkbook Wb
End If
End Sub
And then Public Sub DoThingWithCaseDetailWorkbook(ByVal book As Workbook) could be defined in any standard module and have access to any module state/variable that was set before the WorkbookOpen event fired.
Now the fact that the file is being opened in protected mode (from an untrusted location?) might interfere with this, but then there should be a way to get the script to save the file elsewhere if it's a problem.
Inherited a rather large selection of spreadsheets for multiple clients.
Some of these have multiple ThisWorkbooks (e.g. ThisWorkbook, ThisWorkbook2, etc...).
Trying to put some event code check to automatically run when the workbook is opened. Either Workbook or App Events could be a solution.
The recommendation from http://www.cpearson.com/Excel/AppEvent.aspx suggests adding something like the following code to ThisWorkbook.
Private XLApp As CExcelEvents
Private Sub Workbook_Open()
Set XLApp = New CExcelEvents
End Sub
The issue is that if there are multiple ThisWorkbooks, the code never runs.
Actually, testing shows if I put it into ThisWorkbook1, it runs from there. LOL.
Main Question: Is there an event to create an Application Events object that doesn't use ThisWorkbook when opening a spreadsheet?
Basically another "code gate" that is always guaranteed to run that doesn't require ThisWorkbook.
I suspect "No", but any confirmation or alternative would be helpful.
Thanks.
The Workbook.Open event is the modern way to get code to run on open.
The legacy way is to have a specially named macro in a standard module:
Public Sub Auto_Open()
MsgBox "Works!"
End Sub
That should pop the message box on open.
As mentioned in the comments, a sane Excel file only has a single Workbook module - there being more than one means the file is corrupted in some way, and that can't be good. I'd recommend rebuilding the broken files: you never know when a corrupted file will just outright crash Excel for no apparent reason.
Don't know why the corruption is happening, but it's happening enough across various spreadsheets to be worrisome.
Created code to test for a bad ThisWorkbook upon opening and not allow saving if ThisWorkbook is bad.
1 regular module (modAutoOpenAndClose) and 1 class module (classWBEvents).
Putting these 2 modules into all spreadsheets as standard procedure. As mentioned above in the comments, users will then follow the instructions from Excel Experts Tips to Fix Corrupt Spreadsheet.
Hopefully these are useful to anyone else experiencing/preventing corruption. May seem like overkill, but this has been a bedeviling issue.
Option Explicit
'modAutoOpenAndClose
Dim WorkbookEvents As classWBEvents
Function ErrorIsThisWorkBookBad() As Boolean
On Error GoTo ErrLabel
ErrorIsThisWorkBookBad = Not (ThisWorkbook.CodeName = "ThisWorkbook")
Exit Function
ErrLabel:
ErrorIsThisWorkBookBad = True
End Function
Private Sub Auto_Open()
If ErrorIsThisWorkBookBad Then
Call MsgBox("This Spreadsheet is Corrupt." + vbCrLf + vbCrLf + _
"Please Copy Tabs And Modules to New Spreadsheet." + vbCrLf + vbCrLf + _
"Saving will not be allowed.", vbCritical)
End If
Set WorkbookEvents = New classWBEvents
Set WorkbookEvents.WB = ActiveWorkbook
If Not WorkbookEvents.WB Is Nothing Then
WorkbookEvents.WB_Open
End If
End Sub
Private Sub Auto_Close()
If Not WorkbookEvents Is Nothing Then
If Not WorkbookEvents.WB Is Nothing Then
Set WorkbookEvents.WB = Nothing
End If
Set WorkbookEvents = Nothing
End If
End Sub
And
Option Explicit
'classWBEvents
Public WithEvents WB As Workbook
Private Sub WB_BeforeSave(ByVal SaveAsUI As Boolean, Cancel As Boolean)
If ErrorIsThisWorkBookBad Then
Call MsgBox("This Spreadsheet is Corrupt." + vbCrLf + vbCrLf + _
"Please Copy Tabs And Modules to New Spreadsheet." + vbCrLf + vbCrLf + _
"Saving will not be allowed.", vbCritical)
Cancel = True
End If
End Sub
Public Sub WB_Open()
'Reserved for project specific code
End Sub
I am trying to run foo in Book1.xlsm that runs another sub, bar, in Book2.xlsm. Following is the code for both:
In Book1:
Sub foo()
Dim oldBook As Workbook: Set oldBook = ActiveWorkbook
Dim newBook As Workbook: Set newBook = Workbooks("Book2.xlsm")
Application.Run "'Book2.xlsm'!Module1.bar", oldBook
End Sub
In Book2:
Sub bar(book As Workbook)
book.Close False
Debug.Print "Closed Workbook!"
MsgBox "Closed workbook!"
End Sub
The execution of foo runs perfectly fine and the control is passed onto bar in Book2.xlsm. The line book.Close false runs perfectly fine (closes Book1.xlsm), but code execution stops immediately without any warning (no messages in console or popup). Shouldn't both lines be executed in Book2.xlsm since the control was passed to bar?
I tried this by calling bar workbooks(1) (workbooks(1) = Book1.xlsm) and the entirety of the code runs perfectly fine, as expected. Shouldn't the same happen
How do I preserve code execution in the first scenario, i.e. foo runs bar, change workbooks, close Book1.xlsm and continue execution of bar?
Reconsider who's responsible for what. It's not normal that a workbook is just given any random Workbook object and proceeds to Close it.
Sure you can hack around the fact that you're essentially closing the owner of the call stack, but the root of the problem is, when you have WorkbookA responsible for doing something, and that something involves opening or creating WorkbookB, then it's WorkbookA's responsibility to close WorkbookB when it's done.
Book2 has no business closing anything. Assuming the real macro actually does something more than just closing it, then it should do its thing and then let the caller decide whether to close the workbook or leave it open; Book2!Bar is given a resource that it doesn't own: it's beyond its responsibility to close that resource.
Maybe this can serve as a simple illustration:
Public Sub DoSomething()
Set t = New Thing
UseTheThing t
t.SomeMethod ' error 91: object reference is gone!
End Sub
Private Sub UseTheThing(ByRef t As Thing)
t.Foobar 42
Set t = Nothing ' not your job!
End Sub
Working around the natural order of things (caller -> callee -> back to caller) is neither recommended nor useful. Something is broken in the bigger picture - take a step back and fix the higher level instead of fighting the entire paradigm.
You can use Application.OnTime to accomplish this task.
Book2.xlsm
Public wb As Workbook
Public Sub bar(book As Workbook)
'set the variable for the workbook we want to close - We do this because we cannot pass a workbook object
Set wb = book
'set it to call 1 second from now
Application.OnTime DateTime.DateAdd("s", 1, DateTime.Now), "CloseIt"
End Sub
Sub CloseIt()
wb.Close False
Debug.Print "Closed Workbook!"
MsgBox "Closed workbook!"
End Sub
Even if you use an add-in you may face this problem. I found using the below function very useful in handling such problems. Your call stack root won't be in Book1:
Application.OnTime Now + TimeValue("00:00:01"), "'Book2.xlsm'!Module1.bar", oldBook
I added code to a series of similar files for different projects.
I defined a (Control + R) "^ + R" shortcut to let users see the current record in a userform.
I added application.key code on workbook activating and deactivating event, so the shortcut can be used when several files of this type are open.
My problem is even if the form opens and reads the data from the respective workbook, the userform is not called from the respective file!
Should I localize the procedure as well?
Here are my codes:
The Code in ThisWorkbook
Private Sub Workbook_Activate()
With Application
.OnKey "^R", "ReadCurrentRecord"
End With
End Sub
Private Sub Workbook_Deactivate()
Application.OnKey "^R"
End Sub
The Code in a general Module
Sub ReadCurrentRecord()
If ActiveCell.Worksheet.Name = "OSW" Then
If OSWRng Is Nothing Then
Set OSW = ThisWorkbook.Sheets("OSW")
Set OSWRng = OSW.Range("a5:bz2000")
End If
FrmWODetails.Tag = OSW.Cells(ActiveCell.Row, 14)
FrmWODetails.UserForm_Activate
FrmWODetails.Show vbModeless
End If
End Sub
All the Names are same in the files.
thisworkbook and activecell may not be what you expect them to be.
if this is an addin, this workbook will refer to the addin while active cell wont. i don't even know if activate a cell can be used in an add-in' sheet... I'm thinking not, I'll test that then i get to s computer for my own knowledge.
it's almost always best to fully qualify the object, sometimes from the application level but normally the workbook will suffice
personally, I prefer codenames over names and babes over indexes but they have a few drawbacks that keep them from being my#1 choice
I have an excel book titled Can Opener that is opened by my task scheduler. It opens another workbook on a shared drive that my task scheduler cannot access, and then closes itself.. Can Opener works fine. The problem I am having is that the other workbook has code that, upon open, if it is 7pm system time executes an update macro to get new data from the servers. Once this is done the file saves and closes. The data workbook opens, but no update is occurring, and it does not automatically save and close. I have tied breaking the code down so it would at least update, and this is not working either. I have checked several forums and other locations and I still cannot figure out what the problem is. Can I get a little help?
Sub DataBook_Open()
Application.EnableCancelKey = xlDisabled
If Hour(Now) = 7 And Weekday(Now, vbSunday) < 7 Then
Run_Update
Me.Save
Application.Quit
Else: Me.Save
Application.Quit
End If
End Sub
Run_Update is the name of the macro in my module that merely executes that other macros in the module that create the server connection and run the SQL's. This works fine if manually ran, or run with a button, so I know the error is not here. Also I am trying to run this code every night at 7pm.
I really appreciate any help I can get guys.
Are you sure DataBook_Open() executes at all?
It might not unless you're calling it from Auto_Open() or Workbook_Open().
Try this minimal test files. Replacing the paths with your actual folders.
--can_opener.xlsm--
Sub OpenOtherWorkbook()
Dim sWbkPath As String
sWbkPath = ThisWorkbook.Path & "\" & "test_data.xlsm"
Dim wbkData As Workbook
Set wbkData = Workbooks.Open(sWbkPath)
End Sub
--test_data.xlsm-- in the ThisWorkbook Object
Private Sub Workbook_Open()
If Hour(Now) = 9 Then 'replace with your condition
UpdateData
ThisWorkbook.Save
DoEvents
Else
DoEvents
End If
ThisWorkbook.Close
'Application.Quit
End Sub
Sub UpdateData()
ThisWorkbook.Sheets(1).Range("A1").Value = Format(Now, "yyyy-mm-dd:hh\hmm")
End Sub