Open Hyperlinks Using VBA in Excel (Runtime Error 9) - vba

I am trying to use VBA to open hyperlinks from my excel using the following code:
numRow = 1
Do While WorksheetFunction.IsText(Range("E" & numRow))
ActiveSheet.Range("E" & numRow).Hyperlinks(1).Follow
numRow = numRow + 1
Loop
However, I keep getting Runtime Error 9: Subscript out of range at the point in the code where I follow the hyperlinks.
I'm pretty new to VBA Macro-making (as in-'never done it before'), so help would be appreciated. (And if there's a better way to open a link from each cell in a single column, I'd appreciate learning about that too)
EDIT (To add more Info)
The hyperlink in question has been created using HYPERLINK Worksheet function and the text does not display the link URL. Sample of worksheet data is something like this:
What It Looks Like
Case ------ Link
Case1----- Summary
Case2----- Summary
Case3----- Summary
The Cells showing the text "Summary", however, contain a formula
=HYPERLINK("whateverthebaseurlis/"&[#[Case]]&"/Summary", "Summary")
And this is the link that has to be followed. The link works, it can be followed manually. But I need to do it via macro
Thanks

Probably, you are getting error because you have some cells with text but no link!
Check for link instead of whether or not cell is text:
numRow = 1
Do While ActiveSheet.Range("E" & numRow).Hyperlinks.Count > 0
ActiveSheet.Range("E" & numRow).Hyperlinks(1).Follow
numRow = numRow + 1
Loop

If it is throwing the error where you try to open hyperlinks, try and explictly open it using explorer.exe
Shell "explorer.exe " & Range("E" & numRow).Text
the reason Hyperlinks(1).Follow not working is that is no conventional hyperlink in the cell so it will return out of range
numRow = 1
Do While WorksheetFunction.IsText(Range("E" & numRow))
URL = Range("E" & numRow).Text
Shell "C:\Program Files\Internet Explorer\iexplore.exe " & URL, vbNormalNoFocus
numRow = numRow + 1
Loop
Check this post for a similar problem:
http://www.mrexcel.com/forum/excel-questions/381291-activating-hyperlinks-via-visual-basic-applications.html

TRIED AND TESTED
Assumptions
I am covering 3 scenarios here as shown in the Excel file.
=HYPERLINK("www."&"Google"&".Com","Google"). This hyperlink has a friendly name
www.Google.com Normal hyperlink
=HYPERLINK("www."&"Google"&".Com") This hyperlink doesn't have a friendly name
Screenshot:
Logic:
Check what kind of hyperlink is it. If it is other than which has a friendly name then the code is pretty straightforward
If the hyperlink has a friendly name then what the code tries to do is extract the text "www."&"Google"&".Com" from =HYPERLINK("www."&"Google"&".Com","Google") and then store it as a formula in that cell
Once the formula converts the above text to a normal hyperlink i.e without the friendly name then we open it using ShellExecute
Reset the cell's original formula
Code:
Private Declare Function ShellExecute _
Lib "shell32.dll" Alias "ShellExecuteA" ( _
ByVal hWnd As Long, ByVal Operation As String, _
ByVal Filename As String, Optional ByVal Parameters As String, _
Optional ByVal Directory As String, _
Optional ByVal WindowStyle As Long = vbMinimizedFocus _
) As Long
Sub Sample()
Dim sFormula As String
Dim sTmp1 As String, sTmp2 As String
Dim i As Long
Dim ws As Worksheet
'~~> Set this to the relevant worksheet
Set ws = ThisWorkbook.Sheets(1)
i = 1
With ActiveSheet
Do While WorksheetFunction.IsText(.Range("E" & i))
With .Range("E" & i)
'~~> Store the cells formula in a variable for future use
sFormula = .Formula
'~~> Check if cell has a normal hyperlink like as shown in E2
If .Hyperlinks.Count > 0 Then
.Hyperlinks(1).Follow
'~~> Check if the cell has a hyperlink created using =HYPERLINK()
ElseIf InStr(1, sFormula, "=HYPERLINK(") Then
'~~> Check if it has a friendly name
If InStr(1, sFormula, ",") Then
'
' The idea here is to retrieve "www."&"Google"&".Com"
' from =HYPERLINK("www."&"Google"&".Com","Google")
' and then store it as a formula in that cell
'
sTmp1 = Split(sFormula, ",")(0)
sTmp2 = "=" & Split(sTmp1, "HYPERLINK(")(1)
.Formula = sTmp2
ShellExecute 0, "Open", .Text
'~~> Reset the formula
.Formula = sFormula
'~~> If it doesn't have a friendly name
Else
ShellExecute 0, "Open", .Text
End If
End If
End With
i = i + 1
Loop
End With
End Sub

A cleaner way of getting cells hyperlinks:
Using Range.Value(xlRangeValueXMLSpreadsheet), one can get cell hyperlink in XML. As so, we only have to parse XML.
'Add reference to Microsoft XML (MSXML#.DLL)
Function GetHyperlinks(ByVal Range As Range) As Collection
Dim ret As New Collection, h As IXMLDOMAttribute
Set GetHyperlinks = ret
With New DOMDocument
.async = False
Call .LoadXML(Range.Value(xlRangeValueXMLSpreadsheet))
For Each h In .SelectNodes("//#ss:HRef")
ret.Add h.Value
Next
End With
End Function
So you can use this function in your code as this:
numRow = 1
Do While WorksheetFunction.IsText(Range("E" & numRow))
FollowHyperlink GetHyperlinks(ActiveSheet.Range("E" & numRow))
numRow = numRow + 1
Loop
If you don't need numRow, you can just:
Dim h as String
For Each h In GetHyperlinks(ActiveSheet.Range("E:E"))
FollowHyperlink h
Next
For FollowHyperlink, I suggest below code - you have other options from another answers:
Sub FollowHyperlink(ByVal URL As String)
Shell Shell "CMD.EXE /C START """" """ & URL & """"
End Sub

Related

Excel VBA Onaction with .Select or .ScrollColumn

Good Morning everyone,
I am facing a strange Problem in Excel VBA.
So, I have this minimal Example. The only thing it's supposed to do is, add a Button to the Rightklick context menu. This button should then select a cell.
I searched a bit on StackOverflow and found a solution to passing string arguments in .onaction. But then it gets tricky. I can assign a Range and I can Print the Address and the second Argument in a Mesgbox. But I can't set Breakpoints and even stop doesn't work, nor will .select or .ScrollColumn do anything.
To Replicate just copy the Following code into a standard Module and Execute AddContextmenu to add the Button to the Contextmenu.
Option Explicit
Public Sub AddContextmenu()
Dim MySubMenu As CommandBarControl
Dim i As Long
'Clear Previous Menu Items
For Each MySubMenu In Application.CommandBars("Cell").Controls
If Not MySubMenu.BuiltIn Then
MySubMenu.Delete
End If
Next
'add menu
AddScrollButtons Application.CommandBars("Cell"), 1
End Sub
Public Sub AddScrollButtons(ByVal ContextMenu As CommandBar, ByVal baseindex As Long)
Dim cbb As CommandBarButton
Dim sFunction As String
'Add Button
Set cbb = ContextMenu.Controls.Add(Temporary:=True)
With cbb
sFunction = BuildProcArgString("ScrolltoColTest", "$F$10", "TestArg") ' Get Onaction string
.OnAction = sFunction
.Caption = "Scroll Tester"
.Style = msoButtonAutomatic
End With
End Sub
Function BuildProcArgString(ByVal ProcName As String, ParamArray Args() As Variant)
Dim tempArg As Variant
Dim temp As String
For Each tempArg In Args
temp = temp + Chr(34) + tempArg + Chr(34) + ","
Next
BuildProcArgString = "'" & ThisWorkbook.Name & "'!" & ProcName + "(" + Left(temp, Len(temp) - 1) + ")" ' (Workbook has to be included to ensure that the sub will be executed in the correct workbook)
End Function
Public Sub ScrolltoColTest(Addr As String, OtherArg As String)
Dim cell As Range
Set cell = ActiveSheet.Range(Addr) 'Get Cell that sould be selected from Addr
MsgBox cell.Address & vbNewLine & OtherArg 'Test if the Arguments have been passed correctly and the cell has been assigned
Stop 'Why doesn' this stop?
cell.Select 'Why doesn't this do anything
ActiveWindow.ScrollColumn = cell.Column 'Why doesn't this do anything
End Sub
As you will see in ScrolltoColTest the Part after the Msgbox will not work at all.
Does anyone know why that happens?

VBA for loop takes too long

Just starting out on VBA, and my code is painfully slow. I have a number of workbooks on a network drive, each with several worksheets. I am trying to fetch data from a number of non-contiguous ranges in each worksheet to a pre-designed worksheet, using the following code:
Private Function GetValue(path, file, sheet, ref)
'retrieve value
'// code
GetValue = ExecuteExcel4Macro(arg)
End Function
Sub UpdateModel1()
sheet = "blah blah"
Application.ScreenUpdating = False
'Outputs
destRow = 31 'destination row
destCol = 6 'destination column
srcRow = 50 'source row
For C = 23 To 31 'loop through source columns
ref = Cells(srcRow, C).Address
Cells(destRow, destCol) = GetValue(path, file, sheet, ref)
destCol = destCol + 1
Next C
Application.ScreenUpdating = True
End Sub
However, using a nested for loop in the sub procedure takes way too long. Any suggestions on how to improve this code? PS: this is an amateur's code, and I am just looking for something that does the work decently.
Here is a working example of what you are trying to do:
Sub TestGetSheetValue()
MsgBox GetSheetValue("N:\Tax Documents\", "Tax Stuff.xlsx", "Sheet1", "R1C3")
End Sub
Function GetSheetValue(strPath As String, strFile As String, strSheet As String, strCellRef As String)
GetSheetValue = ExecuteExcel4Macro("'" & strPath & "[" & strFile & "]" & strSheet & "'!" & strCellRef)
End Function
Obviously change what you pass into the function for your needs, note the address is R1C1 format.
The strSheet is the sheet name, not the sheet index.
Make sure you have a trailing \ on the path
Finally, try not to use names like file, path and sheet for variable names.

AutoFilter method of Range class failed in VB.NET

I am trying to use some Parsing i was able to tweak a little. If I use it in straight VBA in excel, it works fine. However, when I use the same code as a module in VB.NET I get the error in the title on the line of code
ws.Range(vTitles).AutoFilter()
(duh!) I am not sure what is going wrong in the conversion, since I am not a hardcore VB.Net programmer, so I am doing a lot of googling, but not finding much that works. Any ideas on how this could be fixed or do I have to abandon the idea of using this snippet in VB.Net?
Here is the code I am using:
'turned strict off or autofilter per http://www.pcreview.co.uk/threads/autofilter-method-of-range-class-failed.3994483/
Option Strict Off
Imports xl = Microsoft.Office.Interop.Excel
Module ParseItems
Public Sub ParseItems(ByRef fileName As String)
'Jerry Beaucaire (4/22/2010)
'Based on selected column, data is filtered to individual workbooks are named for the value plus today's date
Dim wb As xl.Workbook
Dim xlApp As xl.Application
Dim LR As Long, Itm As Long, MyCount As Long, vCol As Long
Dim ws As xl.Worksheet, MyArr As Object, vTitles As String, SvPath As String
'Set new application and make wb visible
xlApp = New xl.Application
xlApp.Visible = True
'open workbook
wb = xlApp.Workbooks.Open(fileName)
'Sheet with data in it
ws = wb.Sheets("Original Data")
'Path to save files into, remember the final "\"
SvPath = "G:\MC VBA test\"
'Range where titles are across top of data, as string, data MUST have titles in this row, edit to suit your titles locale
vTitles = "A1:L1"
'Choose column to evaluate from, column A = 1, B = 2, etc.
vCol = xlApp.InputBox("What column to split data by? " & vbLf & vbLf & "(A=1, B=2, C=3, etc)", "Which column?", 1, Type:=1)
If vCol = 0 Then Exit Sub
'Spot bottom row of data
LR = ws.Cells(ws.Rows.Count, vCol).End(xl.XlDirection.xlUp).Row
'Speed up macro execution
'Application.ScreenUpdating = False
'Get a temporary list of unique values from key column
ws.Columns(vCol).AdvancedFilter(Action:=xl.XlFilterAction.xlFilterCopy, CopyToRange:=ws.Range("EE1"), Unique:=True)
'Sort the temporary list
ws.Columns("EE:EE").Sort(Key1:=ws.Range("EE2"), Order1:=xl.XlSortOrder.xlAscending, Header:=xl.XlYesNoGuess.xlYes, _
OrderCustom:=1, MatchCase:=False, Orientation:=xl.Constants.xlTopToBottom, DataOption1:=xl.XlSortDataOption.xlSortNormal)
'Put list into an array for looping (values cannot be the result of formulas, must be constants)
MyArr = xlApp.WorksheetFunction.Transpose(ws.Range("EE2:EE" & ws.Rows.Count).SpecialCells(xl.XlCellType.xlCellTypeConstants))
'clear temporary worksheet list
ws.Range("EE:EE").Clear()
'Turn on the autofilter, one column only is all that is needed
ws.Range(vTitles).AutoFilter()
'Loop through list one value at a time
For Itm = 1 To UBound(MyArr)
ws.Range(vTitles).AutoFilter(Field:=vCol, Criteria1:=MyArr(Itm))
ws.Range("A1:A" & LR).EntireRow.Copy()
xlApp.Workbooks.Add()
ws.Range("A1").PasteSpecial(xl.XlPasteType.xlPasteAll)
ws.Cells.Columns.AutoFit()
MyCount = MyCount + ws.Range("A" & ws.Rows.Count).End(xl.XlDirection.xlUp).Row - 1
xlApp.ActiveWorkbook.SaveAs(SvPath & MyArr(Itm), xl.XlFileFormat.xlWorkbookNormal)
'ActiveWorkbook.SaveAs SvPath & MyArr(Itm) & Format(Date, " MM-DD-YY") & ".xlsx", 51 'use for Excel 2007+
xlApp.ActiveWorkbook.Close(False)
ws.Range(vTitles).AutoFilter(Field:=vCol)
Next Itm
'Cleanup
ws.AutoFilterMode = False
MsgBox("Rows with data: " & (LR - 1) & vbLf & "Rows copied to other sheets: " & MyCount & vbLf & "Hope they match!!")
xlApp.Application.ScreenUpdating = True
End Sub
End Module
Looks like you need to specify at least one optional parameter. Try this:
ws.Range(vTitles).AutoFilter(Field:=1)
I realize this was closed years ago, but I recently ran into this problem and wanted to add to the solution.
This seems to only work when specifically using the first optional Field parameter. I attempted this fix using the optional VisibleDropDown parameter and still got this error.
ws.Range["A1"].AutoFilter(VisibleDropDown: true); Gives error
ws.Range["A1"].AutoFilter(Field: 1); No error

Excel Transform Sub to Function

I am quite new in VBA and wrote a subroutine that copy-paste cells from one document into another one. Being more precise, I am working in document 1 where I have names of several product (all in column "A"). For these product, I need to look up certain variables (e.g. sales) in a second document.
The subroutine is doing the job quite nicely, but I want to use it as a funcion, i.e. I want to call the sub by typing in a cell "=functionname(productname)".
I am grateful for any helpful comments!
Best, Andreas
Sub copy_paste_data()
Dim strVerweis As String
Dim Spalte
Dim Zeile
Dim findezelle1 As Range
Dim findezelle2 As Range
Dim Variable
Dim Produkt
'Variable I need to copy from dokument 2
Variable = "frequency"
'Produkt I need to copy data from document 2
Produkt = Cells(ActiveCell.Row, 1)
'path, file and shhet of document 2
Const strPfad = "C:\Users\Desktop\test\"
Const strDatei = "Bezugsdok.xlsx"
Const strBlatt = "post_test"
'open ducument 2
Workbooks.Open strPfad & strDatei
Worksheets(strBlatt).Select
Set findezelle = ActiveSheet.Cells.Find(Variable)
Spalte = Split(findezelle.Address, "$")(1)
Set findezelle2 = ActiveSheet.Cells.Find(Produkt)
Zeile = Split(findezelle2.Address, "$")(2)
'copy cell that I need
strZelle = Spalte & Zeile 'Zelladresse
strVerweis = "'" & strPfad & "[" & strDatei & "]" & strBlatt & "'!" & strZelle
'close document 2
Workbooks(strDatei).Close savechanges:=False
With ActiveCell
.Formula = "=IF(" & strVerweis & "="""",""""," & strVerweis & ")"
.Value = .Value
End With
End Sub
Here is an example to create a function that brings just the first 3 letters of a cell:
Public Function FirstThree(Cell As Range) As String
FirstThree = Left(Cell.Text, 3)
End Function
And using this in a Excel worksheet would be like:
=FirstThree(b1)
If the sub works fine and you just want to make it easier to call you can add a hotkey to execute the Macro. In the developer tab click on Macros then Options. You can then add a shortcut key (Crtl + "the key you want" it can be a shortcut key already used like C, V, S, but you will lose those functions (Copy, Paste Save, Print)
enter image description here

How to use a variable as one of the values in Excel VBA VLOOKUP

I'm using VBA in Excel and I'm assigning a VLOOKUP as a formula to a cell. It works fine, but I would like to use a variable that refers to the last cell that contains a value in the column.
In the example below, I would the value for $B$269 to change depending on the number of elements in the closed document.
"=VLOOKUP(B2,'Macintosh HD:Users:myself:Documents:[Master_Terms_Users.xlsm]Master_Terms_Users.csv'!$A$1:$B$269,2,FALSE)"
I know I want to use something along the lines of:
Range("B" & Rows.Count).End(xlUp).Address
With that said, I haven't been able to figure out how to incorporate the result, which is something like $B$269 into the VLOOKUP. I know that those formulas return the correct address because I've used it in Debug.Print.
I tried to do something like this:
"=VLOOKUP(B2,'Macintosh HD:Users:myself:Documents:[Master_Terms_Users.xlsm]Master_Terms_Users.csv'!$A$1:"&GetLastRowFunct&",2,FALSE)"
But that didn't work.
Here is my current code:
Sub GetLastRow()
Debug.Print GetLastRowFunct
End Sub
Function GetLastRowFunct() As String
Dim openNwb As Workbook
Const MasterPath = "Macintosh HD:Users:myself:Documents:"
Dim strNewFileName As String
strNewFileName = "Master_Terms_Users.xlsm"
Set openNwb = Workbooks.Open(MasterPath & strNewFileName)
Dim openNws As Worksheet
Set openNws = openNwb.Worksheets(1)
GetLastRowFunct = openNws.Range("B" & Rows.Count).End(xlUp).Address
openNwb.Close
End Function
Any recommendations would be appreciated.
I would rewrite that function to return the entire range address, including worksheet, workbook and path.
Function GetLastRowFunct() As String
Const MasterPath = "Macintosh HD:Users:myself:Documents:"
Dim openNwb As Workbook, strNewFileName As String
strNewFileName = "Master_Terms_Users.xlsm"
Set openNwb = Workbooks.Open(MasterPath & strNewFileName)
with openNwb.Worksheets(1)
GetLastRowFunct = .Range(.cells(1, 1), .cells(rows.count, "B").End(xlUp)).Address(1, 1, external:=true)
end with
openNwb.Close
End Function
The formula construction and assignment becomes simpler to deal with.
rng.formula = "=VLOOKUP(B2, " & GetLastRowFunct & ", 2, FALSE)"
tbh, I'm not sure if you have to supply your own square brackets or not on a Mac.