VBA keep track of which workbooks are open - vba

How can VBA keep track of which workbooks are open?
I am writing a data mining macro that takes information from a variable number of workbooks. The user may choose what workbooks will be parsed via a userform. However, workbooks can be opened and closed while the userform is running.
So, how can my userform keep track of which workbooks are open, so that it can display them accurately.
Right now, I'm using a recursive function that calls itself with "Application.OnTime". I don't really like this solution for because it involves extra checks to see if the userform is still open, as well as having a delay because of whatever period the function is called at.
Final: Combined solution from answers and comments
userform code, requires a ListBox called WorkbookList, and a Textbox called FileTextBox
Private WithEvents App As Application
Public Sub WorkbookList_UpdateList()
WorkbookList.Clear
For Each Wb In Application.Workbooks
WorkbookList.AddItem Wb.name
Next Wb
End Sub
Private Sub WorkbookList_Change()
If WorkbookList.ListIndex = -1 Then Exit Sub
key = WorkbookList.List(WorkbookList.ListIndex)
For Each Wb In Application.Workbooks
IsWorkBookOpen Wb.path
If Wb.name = key Then FileTextbox.text = Wb.path
Next Wb
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
WorkbookList_UpdateList
End Sub
Private Sub App_NewWorkbook(ByVal Wb As Workbook)
WorkbookList_UpdateList
End Sub
Private Sub App_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
Application.OnTime Now + TimeValue("00:00:01"), "WorkbookClosed"
End Sub
Private Sub UserForm_Initialize()
Set App = Application
WorkbookList_UpdateList
UpdatePeriodicly
End Sub
module code (put this in a vba module):
'Code From: http://www.ozgrid.com/forum/showthread.php?t=152892
Function IsUserFormLoaded(ByVal UFName As String) As Boolean
Dim UForm As Object
IsUserFormLoaded = False
For Each UForm In VBA.UserForms
If UForm.name = UFName Then
IsUserFormLoaded = True
Exit For
End If
Next
End Function
Public Sub WorkbookClosed()
If IsUserFormLoaded("InputForm") = False Then Exit Sub
InputForm.WorkbookList_UpdateList
End Sub

You can use Application Events fgor this.
E.g. see cpearson.com/excel/appevent.aspx
Private WithEvents app As Excel.Application
Sub Init()
Set app = Application 'start capturing events
End Sub
Private Sub app_NewWorkbook(ByVal Wb As Workbook)
Debug.Print "New"
End Sub
Private Sub app_WorkbookBeforeClose(ByVal Wb As Workbook, Cancel As Boolean)
Debug.Print "Before close: " & Wb.Name
End Sub
Private Sub app_WorkbookOpen(ByVal Wb As Workbook)
Debug.Print "Open: " & Wb.Name
End Sub

So I think that this is an interesting script and something that might be useful for the problem your trying to solve.
Public Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False 'Workbook IS NOT
Case 70: IsWorkBookOpen = True 'Workbook IS open
Case Else: Error ErrNo
End Select
End Function
You can call this by doing something like this
Ret = IsWorkBookOpen("C:\test.xlsm")
If Ret = True Then 'YOUR CODE HERE

Related

Close another workbook upon closing the workbook

I have a code that opens another workbook (source.xlsx) when I open (triggers on Workbook_Open event) a template workbook (template.xlsm).
The code:
Private Sub Workbook_Open()
Application.Screenupdating= False
Set w = workbooks
w.open filename:="link", Updatelinks:=true , readonly:=true
activewindow.visible=false
thisworkbook.activate
application.screenupdating=True
end sub
However, I want the source workbook to just run on background upon opening and close it when I close the template file.
Private sub workbook_aftersave()
Workbook("source.xlsx").Close SaveChanges:=False
End Sub
You want to use the Workbooks collection (Workbooks("source.xlsx")), rather than a Workbook object (Workbook("source.xlsx"), which will throw an error). Also, rather than trying to close it on the Workbook_AfterSave event, you could try using the Workbook_BeforeClose event:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
On Error Resume Next 'In case the Workbook is already closed
Workbooks("source.xlsx").Close SaveChanges:=False
End Sub
{EDIT} And, because I can, here's a tidier version of your Workbook_Open code too:
Private Sub Workbook_Open()
Application.ScreenUpdating = False
Dim wsSource As Workbook
Set wsSource = Workbooks.Open(Filename:="SomeDirectory\source.xlsx", UpdateLinks:=True, ReadOnly:=True) 'Change the filename to where your "source.xlsx" is stored
DoEvents 'Wait for it to finish opening
wsSource.Windows(1).Visible = False
ThisWorkbook.Activate
Application.ScreenUpdating = True
End Sub
Try using
Application.Visible = False
Place this in your Workbook_Open code on the workbook you want to hide, and then use True to bring it back to close.

Automatically open an excel file to cell A1 in all worksheets (using VBA)

I am trying to write VBA code so that whenever I open any file in excel, it automatically goes to Cell A1 in all sheets (no matter what cells were selected when it was last saved). I found something online that suggested putting the following code in my Personal .xlsb project:
Sub kTest()
Dim i As Long, s() As String, a As String, n As Long
With ActiveWorkbook
For i = 1 To .Worksheets.Count
a = a & .Worksheets(i).Name
n = n + 1
ReDim Preserve s(1 To n)
s(n) = .Worksheets(i).Name
If Len(a) > 224 Then
.Worksheets(s).Select
.Worksheets(s(1)).Activate
[a1].Select
n = 0: a = "": Erase s
End If
Next
If Len(a) Then
.Worksheets(s).Select
.Worksheets(s(1)).Activate
[a1].Select
End If
Application.Goto .Worksheets(1).Range("a1")
End With
End Sub
But nothing happens when I open a file. Please help!
You cannot go to Cell A1 in every sheet. But if you would like to go to Cell A1 of a single sheet you could do the following.
Create a class ExcelEvents with the following code
Option Explicit
Private WithEvents App As Application
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
App.Goto Wb.Worksheets(1).Range("A1")
End Sub
Private Sub Class_Initialize()
Set App = Application
End Sub
And in ThisWorkbook add
Option Explicit
Private xlApp As ExcelEvents
Private Sub Workbook_Open()
Set xlApp = New ExcelEvents
End Sub
Save the workbook, re-open it and the code in the workbook_open event will run and that means as soon as you open another workbook the code will goto cell A1 of sheet 1
EDIT If you really mean to select A1 in every single sheet you could change the code as follows
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
Dim sh As Worksheet
App.ScreenUpdating = False
For Each sh In Wb.Worksheets
sh.Select
sh.Range("A1").Select
Next
App.Goto Wb.Worksheets(1).Range("A1")
App.ScreenUpdating = True
End Sub
A simple solution:
For Each Sheet In ActiveWorkbook.Worksheets
Sheet.Select
Range("A1").Select
Next
Using MicScoPau's loop through the worksheets
Place the following code in the ThisWorkbook module of Personal.xlsb:
You'll have to reopen excel for this to work the first time.
If your Personal.xlsb is hidden, then you will have some issues with the each sheet in activeworkbook.
Private WithEvents app As Application
Private Sub app_WorkbookOpen(ByVal Wb As Workbook)
For Each Sheet In ActiveWorkbook.Worksheets
Sheet.Select
Range("A1").Select
Next
End Sub
Private Sub Workbook_Open()
Set app = Application
End Sub

Worksheet_Change if not as per list in range then don't do

Wanted to know code if blank & if not as per list (in range A:A) then don't do anything. And if found in list then Workbooks.Open. Can any one guide me with correct code in this case.
My current code is below:
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Address = "D11" Then
Workbooks.Open Filename:=ThisWorkbook.Worksheets("sheet1").Range("E11").Value, ReadOnly:=False, Password:=""
End If
End Sub
you could use a hedper function that tries to open some workbook and returns:
False if unsuccessful o
True if successful along with a valid Workbook object reference
like follows:
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim wb As Workbook
If Target.Address = "$D$11" Then
If GetWorkBook(ThisWorkbook.Worksheets("sheet1").Range("E11").Value, wb) Then
With wb 'reference just opened workbook
' your code to exploit opened workbook
End With
End If
End If
End Sub
Function GetWorkBook(fullname As Variant, wb As Workbook) As Boolean
On Error Resume Next 'avoid subsequent error stop the function
Set wb = Workbooks.Open(Filename:=fullname, ReadOnly:=False, Password:="") 'try and open a workbook with passed full name
GetWorkBook = Not wb Is Nothing 'return function result (i.e. 'True' if 'wb is a valid 'Workbook' reference)
End Function

vba Macro to open other WB and execute Macro fails when other WB have userform show on opening

As topic implies I have a problem I cannot find any solution to.
I have a Workbook (1) with the purpose to open other WBs and run macros in them.
Everything works like a charm except when the other WB has Workbook_Open() event to open a Userform (typically it asks if the WB should be updated). Then I get error code 1004 and my code fails.
How could I supress the Workbook_Open event from triggering when I open another WB?
I have tried the setting Application.EnableEvents = False but it´s not related.
Thank you very much for any help on this topic!
Here is the code for opening the WB
Public Function wbTargetOpen(sTargetPath As String, SPassword As String) As Workbook
Dim sWBName As String
sWBName = Mid(sTargetPath, InStrRev(sTargetPath, "\") + 1, Len(sTargetPath) - InStrRev(sTargetPath, "\") + 1)
If WorkbookIsOpen(sWBName) Then
Set wbTargetOpen = Workbooks(sWBName)
If wbTargetOpen.ReadOnly = True Then
wbTargetOpen.Close
Set wbTargetOpen = Workbooks.Open(FileName:=sTargetPath, UpdateLinks:=0, ReadOnly:=False, WriteResPassword:=SPassword)
End If
Else
Set wbTargetOpen = Workbooks.Open(FileName:=sTargetPath, UpdateLinks:=0, ReadOnly:=False, WriteResPassword:=SPassword)
End If
If wbTargetOpen.ReadOnly Then sErrorCode = "ReadOnly"
End Function
All you have to do is add one word VbModeless to the other workbook which launches the userform.
Private Sub Workbook_Open()
UserForm1.Show vbModeless
End Sub
The vbModeless will launch the form but will also allow your macro to run.
Close the other userforms before you run the macros.
Sub CloseOtherUserForms()
Dim frm As UserForm
For Each frm In UserForms
If Not TypeName(frm) = "MacroForm" Then
Unload frm
End If
Next
End Sub

How to make sure that hotkey only launches UserForm for active workbook?

In a template's workbook_open event, I have a hotkey ^m registered to launch a UserForm DataValidation:
Private Sub workbook_open()
Application.OnKey "^m", "launchDataValidation"
End Sub
Sub launchDataValidation()
DataValidation.Show
End Sub
It's possible that there will be multiple workbooks open at the same time that are built from this template. I want to make sure that when the user presses the hotkey, the UserForm that is launched is from the active workbook, so that any operations executed by that UserForm will only affect that workbook.
I tried rewriting launchDataValidation as:
Sub launchDataValidation()
On Error GoTo errHandler
ActiveWorkbook.DataValidation.Show
Exit Sub
errHandler:
End Sub
The intent is that the active workbook's DataValidation form opens, and if the active book doesn't have a DataValidation form, nothing happens. But instead, the ActiveWorkbook.DataValidation.Show call goes to the error handler, suggesting that this is not the correct way to open that form in the active workbook.
I also tried:
Sub launchDataValidation()
Dim bkName As String
Dim runString As String
bkName = ActiveWorkbook.Name
runString = "'" & bkName & "'!DataValidation.Show"
On Error GoTo errHandler
Application.Run runString
Exit Sub
errHandler:
End Sub
This also threw an error.
Figured it out:
Private Sub workbook_open()
Application.OnKey "^m", "launchDataValidation_ActiveBook"
End Sub
Sub launchDataValidation()
DataValidation.Show
End Sub
Sub launchDataValidation_ActiveBook()
Dim bkName As String
Dim runString As String
bkName = ActiveWorkbook.Name
runString = "'" & bkName & "'!launchDataValidation"
On Error GoTo errHandler
Application.Run runString
Exit Sub
errHandler:
End Sub