Normal.dot and Personal.xlsb - excel

For using Word I have many public subs and functions stored in Normal.dot. I can use all of them in any module I write in any other Word project. For instance this elementary function stored in Normal.dot,
Public Function BooleanString(b As Boolean) As String
If b Then BooleanString = "Yes!" Else BooleanString = "No!"
End Function
can be used from other projects as
Sub TestNormal()
Debug.Print BooleanString(True)
End Sub
Using Excel I cannot duplicate this behavior: Public subs and functions I have stored in Personal.xlsb (among then the one above) seem not to be visible when called from a module in the VBAProject belonging to any other spreadsheet than Personal.xlsb, resulting in “Sub or Function not defined”.
I have made sure that the Excel option box “Have trust in the VBA project model” (translated from the Danish version) is checked. Also, using “View”, I have made sure that Personal.xlsb is not hidden; it shows up every time I open Excel.
What am I doing wrong or missing here?
Best regards
Holger Nielsen

In Excel, using Functions/Subs of Personal.xlsb is a little different...
In order to use a UDF function, let us say, testMultiplication:
Function testMultiplication(x As Long, y As Long) As Long
testMultiplication = x * y
End Function
it can be called from the (other) workbook sheet cell in this way:
=Personal.xlsb!testMultiplication(3, 4)
In order to call Personal.xlsb Subs and Functions, a reference to the hidden workbook VBAProject is necessary...
To do that, follow the next steps, please:
Firstly give to the Personal.xlsb VBAProject an elocvent name (also to avoid name conflict error, keeping it like VBAProject):
a) In VBE - Project Explorer, right click on its VBAProject, choose VBAProject Properties... and change the standard name in, let us say, 'PersVBAProject`.
b) Then click on a module of the workbook you want to add the reference and use Tools -> References. Find 'PersVBAProject, check it and press OK`.
Starting from that moment the subs/functions of Prsonal.xlsb can be directly called from the specific workbook
In order to programmatically add the reference, use the next code, please:
Sub AddPersonalXLSBRef()
'It needs a reference to 'Microsoft Visual Basic for Applications Extensibility ...'
Dim VBe As VBIDE.VBe, vbPr As VBIDE.VBProject
Dim objRef As VBIDE.Reference, boolFound As Boolean
Set VBe = Application.VBe
Set vbPr = ActiveWorkbook.VBProject
'check if reference to Personal.xlsb exists:
'Please, take care to have its VBAProject name modified in `PersVBProject`
'or something else and, in such a case, adapt the following lines accordingly
For Each objRef In vbPr.References
If objRef.Name = "PersVBProject" Then
MsgBox "Reference to Personal.xlsb already exists"
Exit Sub
End If
Next
'add the reference, if not exist:
vbPr.References.AddFromFile Application.StartupPath & "\Personal.xlsb"
MsgBox "Reference to Personal.xlsb added Successfully"
End Sub
It adds the reference to the active workbook project. The code can be adapted to add it to all open workbooks (not having it added, yet)...

Related

How to have excel wait for a workbook to open

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.

How to Use the Find Function

I have recently Written some code to take an input from a userform text box and search for it in my database. If found I would like it to return the value and insert it into cell A1149; I have written the below code but it gives me error #424 "Object Required". I am very new to VBA so any and all help is greatly appreciated.
Private Sub CMDSearch_Click()
Dim pesquisa As Range
Set pesquisa = Worksheets("Petrobras").Activate.Range("$W:$W") _
.Find(What:=Opp_Num_Search.Value, LookIn:=xlValues, Lookat:=xlWhole).Activate
Worksheets("Petrobras").Range(A1149).Value = pesquisa.Value
UserForm1.Hide
End Sub
Range.Activate doesn't return anything, it's like a Sub procedure:
Public Sub DoSomething()
' does something...
End Sub
If you did Set foo = DoSomething.Something, you'd get the same error: an object is required, otherwise that .Something member call is illegal (except now the error would be at compile-time and not run-time, because of how binding works).
Set pesquisa = Worksheets("Petrobras").Activate...
You don't want to Activate any sheets.
Part of the problem is the implicit late-binding going on: Worksheets returns an Object, so everything you wrote after that, all these chained member calls, can only be resolved at run-time.
Make it early-bound, by declaring an explicit Worksheet variable:
Dim sheet As Worksheet
Set sheet = Worksheets("Petrobras")
Now if you want to activate it, you can do sheet.Activate (but you don't need to). And if you want to get a Range from that worksheet, you can make a .Range member call, and the IDE will now help you do it:
Dim result As Range
Set result = sheet.Range("$W:$W").Find(...)
NEVER chain any member calls to what Range.Find returns. If the search turned up a result, you have a Range object. If it didn't, you have Nothing - and any member call made against Nothing will always raise run-time error 91.
Validate the search result first:
If result Is Nothing Then
MsgBox "Could not find '" & Opp_Num_Search.Value & "' in column W."
Exit Sub
End If
Or:
If Not result Is Nothing Then
sheet.Range("A1149").Value = result.Value
End If
Note that A1149 is a string literal representing a cell address, and as such it must be surrounded with double quotes ("). If it's not in double quotes and it looks like a valid variable name, VBA will treat it as ..a variable... and that will cause yet another error (1004), because Range will be rather unhappy to work with a Variant/Empty value.
To prevent VBA from "declaring" on-the-fly variables with a typo (and causing hard-to-find bugs), make sure you have Option Explicit at the very top of every module in your project.
One last thing:
UserForm1.Hide
This hides the default instance of UserForm1, which may or may not be the current object / instance that's shown - and the form itself has no way to know how it was shown:
UserForm1.Show '<~ shows the default instance
With New UserForm1
.Show '<~ shows a new (non-default) instance
End With
For this reason, you should avoid referring to the default instance in a form's code-behind. Use Me instead:
Me.Hide
That way you're referring to whatever instance of the form is currently shown, whether that's the default instance or not. See UserForm1.Show for more information, tips, pitfalls and common mistakes involving userforms.
Note that Rubberduck has quite a few inspections that can identify and warn you about (and often, automatically fix) most of these problems. Rubberduck is a free and open-source VBIDE add-in project I manage.

Reopen workbook at last active sheet

How can I reopen my workbook at the last active sheet when I click on a hyperlink that runs ForceReopen? What I have fails because LstSht is not set. (Note that I do not want to save changes when I run ForceReopen.)
' Workbook module
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Set LstSht = Sh
End Sub
' Standard module
Public LstSht As Worksheet
Sub ForceReopen()
Application.OnTime Now + TimeValue("00:00:01"), "GoToLast"
ThisWorkbook.Close False
End Sub
Sub GoToLast()
LstSht.Activate
End Sub
You need to store the name of the last active sheet somwhere. Since you don't want to save the file on close, it can't be in the file itself.
One possibility is to create a small text file that contains just the sheet name to activate on file open. The workbook open event can then read the text file and activate the specified sheet.
The workbook activate event should update the text file. Provide error handlers to allow for the text file not existing, or the specified sheet not existing. Depending on how robust you want to make it, you might need to handle the sheet name changing too.
Location of the text file is a design choice: maybe the same folder as the Excel file, or some fixed config folder.
Another possibility would be to use the registry rather than a text file.
Remember something during automatic reopen within Application.Caption
You may store the ActiveSheet.Name within Application.Caption during a forced reopening of a workbook, even if all other global variables are lost.
' Within ThisWorkbook:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Application.Caption = "Last Active: " & ActiveSheet.Name
' Stop ' temporary to see the caption, but restart will not work!
End Sub
' Both following subs within a normal module:
Public Sub ForceReopen()
Application.OnTime Now + TimeValue("00:00:01"), "GoToLast"
ThisWorkbook.Close False
End Sub
Private Sub GoToLast()
Dim iLastSheet As Integer ' Position of text "Last Active" in Caption
iLastSheet = InStr(Application.Caption, "Last Active:")
' Stop ' temporary to see "Last Active: ..." in the caption
If iLastSheet > 0 Then
On Error Resume Next
ThisWorkbook.Sheets(Mid(Application.Caption, iLastSheet + 13)).Activate
On Error GoTo 0
Application.Caption = ""
End If
End Sub
If you save the workbook staying in Sheet1 and start ForceReopen staying in Sheet3 (by button, by hyperlink, by manual execution, whatever) then it reopens at Sheet3.
To check the functionality, you may add the first Stop to see, if Application.Caption is set correctly (workbook will not open afterwards, so you have to delete this Stop after testing):
You may add the second Stop to check, if Application.Caption is set as intended after it reopened automatically:
There is a logical flaw in your plan. Why should you activate the last viewed sheet and then close the workbook without saving that change?
Set a public variable by the name of LstSht. (I would prefer it to be the sheet name rather than the sheet object but that may be a matter of taste.
Set the variable on the Activate event of each sheet. Note that the Activate event may not occur when you first open the workbook. Therefore the variable must also be set in the Workbook_Open event.
In the GoToLast procedure provide for the eventuality that the variable doesn't hold a value - just in case. Use On Error Resume Next. Also make sure that the sheet isn't activated if it is already active. You may make this procedure a function and let it return True if the sheet was changed (for whatever purpose).
The effect of the whole thing, as you have laid it out, would be that, in the middle of whatever you are doing, the last active sheet is suddenly activated and then the workbook closed without saving your work. Charming idea!

How to make Application.key Shortcuts WorkBook Specific?

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

Running a macro in excel

I don't think this is technically a macro but I don't know what else to call it:
Users want to print individual sections from a report on a sheet. These sections are just named ranges.
Some points:
The file is an xlt file.
It is used as a template to generate an xls file.
I am using Excel 2007, but the users will run a mixture of 2007 and 2003.
When the file loads into excel it asks if I want to enable all macros and I confirm.
The function it is calling is public
I created a series of buttons down the edge of the sheet:
' in a loop
With ActiveSheet.Buttons.Add(rngCurrent.Left + 2, rngCurrent.Top + 1, rngCurrent.Width - 2, rngCurrent.Height - 1)
.Caption = "Print"
.OnAction = "PrintRange"
.Font.Size = 7
.Name = CStr(oSite.SiteID)
End With
However when I click on the button it gives "Cannot run the macro 'filename.xls!PrintRange".
The PrintRange function is in the sheet shtPage while the loop is in a module called modPage.
Why can't I call the function I need and how can I make it work?
Even with your PrintRange sub declared as Public you still need to refer to it more specifically so that Excel can find it, because you have put it in the code section of a worksheet instead of a module.
Change:
.OnAction = "PrintRange"
to
.OnAction = "shtPage.PrintRange"
and it will work just fine.
A caution: If you have renamed your worksheet on the page tab to 'shtPage', but in the VBA project explorer, Excel still refers to the worksheet as 'Sheet1(shtPage)', you will need to use the 'Sheet1' name that Excel recognizes, not the 'shtPage' name that appears on the page tab.
Did you make the PrintRange macro public? It needs to be defined as follows:
Public Sub PrintRange
'// ...'
End Sub

Resources