VBA losing links - excel

Manual - Select range, execute Sub
How it works - Sub saves all non-blank cells to finalArray that is ultimately displayed in the selected range
What's the problem - if range contains cells with hyperlinks created via insert-hyperlink, the hyperlinks disappear.
Sub RemoveBlanks()
'i,j - counters, k - offset
Dim finalArray() As Variant
ReDim finalArray(Selection.Rows.Count, 1)
k = 1
For i = 1 To Selection.Rows.Count
If Selection(i, 1) <> "" Then
finalArray(k, 1) = Selection(i, 1)
k = k + 1
End If
Next i
Application.ScreenUpdating = False
Application.EnableEvents = False
Selection.Clear
For i = 1 To k
Selection(i, 1).Value = finalArray(i, 1)
Next i
End Sub

This Code will loops through each cell in the selected range, checks if the cell has a hyperlink then temporarily grab and store the address that it’s pointing to re-apply the hyperlink
Option Explicit
Sub fixHyperlinks()
Dim rng As Range
Dim address As String
Application.ScreenUpdating = False
For Each rng In Selection
If rng.Hyperlinks.Count > 0 Then
address = rng.Hyperlinks(rng.Hyperlinks.Count).address
rng.Hyperlinks.Add Anchor:=rng, _
address:=address
End If
Next
Application.ScreenUpdating = True
End Sub
After you run this code, you should be able to set in your array the range without losing your links.
Conclusion : Run this code before you run your macro.

So I have found a way around the issue after lurking through internet and trying to save links in another array (fails). It works only if the value in the cell is the same as name of a sheet, yet it solves my issue so far.
Sub CreateLinks()
'i - counter, the title as i=1 is omitted. Code uses value stored in cell to
'transform it into a link.
Dim i As Integer
For i = 2 To Selection.Rows.Count
If Selection(i) <> "" Then
ActiveSheet.Hyperlinks.Add anchor:=Selection(i), _
address:="#'" & CStr(Selection(i)) & "'!A1", _
TextToDisplay:=CStr(Selection(i))
End If
Next i
End Sub

Related

Excel VBA to insert a page break before the row if the last 3 characters of the column are ":01"

I'm a total novice with VBA. The title describes what I'm looking to do. I need a script to sarch a column (in my case, Column A) and if the last 3 characters are ":01" I need a horizontal page break inserted above it. Bonus points if you can make it skip the FIRST instance of ":01" and only insert page breaks on every subsequent appearance of ":01 in the column after that.
I've been accomplshing what I need with a very clunky process, where I insert a row before Row A, then paste this formula into every cell in the column: =IF(RIGHT(B3, 3) = ":01", 1,"")
Then I'll select Special, choose only numbers, and then run this VBA:
Sub AddPgBrk()
For Each Cell In Selection
ActiveWindow.ActiveSheet.HPageBreaks.Add _
Before:=Cell
Next Cell
End Sub
Then I delete Column A. It DOES work but I'd love to do it all in one step with a single VBA.
I tried this, and it doesn't give me any errors, but it also doesn't do anything:
Sub AddPgBrk()
Last = Cells(Columns.Count, "A").End(xlUp).Column
For i = Last To 1 Step -1
If (Right(Cells(i, "A"), 3)) = ":01" Then
ActiveWindow.ActiveSheet.HPageBreaks.Add _
Before:=Cell
End If
Next i
End Sub
Appreciate the look and assistance. Thanks everyone!
I changed a couple of things in your original code, noted below, and added a counter to avoid the first instance.
Sub AddPgBrk()
Dim Last As Long, i As Long, n As Long, j As Long
Last = Cells(Columns.Count, "A").End(xlUp).Row
n = WorksheetFunction.CountIf(Columns(1), "*:01")
For i = Last To 1 Step -1
If (Right(Cells(i, "A"), 3)) = ":01" Then
j = j + 1
If j < n Then
ActiveSheet.HPageBreaks.Add before:=Cells(i, "A")
End If
End If
Next i
End Sub
Add Manual Page Breaks
Sub AddPageBreaks()
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Dim rg As Range
Set rg = ws.Range("A2", ws.Cells(ws.Rows.Count, "A").End(xlUp))
' Remove only all manual horizontal page breaks.
ws.Cells.PageBreak = xlPageBreakNone ' also 'ws.Rows' or 'ws.Columns'
' Remove all manual page breaks (horizontal and vertical).
'ws.ResetAllPageBreaks
' But how to remove only all manual vertical page breaks?
Dim cell As Range
Dim pbCount As Long
Dim FirstFound As Boolean
For Each cell In rg.Cells
If Right(CStr(cell.Value), 3) = ":01" Then
If FirstFound Then
ws.HPageBreaks.Add Before:=cell
'cell.EntireRow.PageBreak = xlPageBreakManual ' much slower
pbCount = pbCount + 1
Else ' skip the first
FirstFound = True
End If
End If
Next cell
MsgBox "Manual horizontal page breaks added: " & pbCount & vbLf _
& "Total horizontal page breaks: " & ws.HPageBreaks.Count, vbInformation
'ws.ExportAsFixedFormat xlTypePDF, "Test.pdf", , True, False, , , True
End Sub

Copying rows based on cell value, not selecting next empty row on destination worksheet

I have written a short VBA code to copy rows from one worksheet "Quote Tracker", to another sheet "Cashflow", once a certain value has been selected in Column "O" (75 - 100%).
The issue I am having is that the rows are not copied into the next available empty row, only further down the sheet. I am also unable to stop the code copying the same line multiple times.
Is there anything I can add to ensure they are always added to the top of the "Cashflow" sheet or next available row?
I am also unable to put anything together to detect duplicates, so if the code is run more than once, it just keeps adding them to the "Cashflow sheet". Can anything be added to stop this?
Here is what I have so far:
Sub MoveRowBasedOnCellValue()
Dim xRg As Range
Dim xCell As Range
Dim I As Long
Dim J As Long
Dim K As Long
I = Worksheets("Quote Tracker").UsedRange.Rows.Count
J = Worksheets("Cashflow").UsedRange.Rows.Count
If J = 1 Then
If Application.WorksheetFunction.CountA(Worksheets("Cashflow").UsedRange) = 0 Then J = 0
End If
Set xRg = Worksheets("Quote Tracker").Range("O1:O" & I)
On Error Resume Next
Application.ScreenUpdating = False
For K = 1 To xRg.Count
If CStr(xRg(K).Value) = "75 - 100%" Then
xRg(K).EntireRow.Copy Destination:=Worksheets("Cashflow").Range("A" & J + 1)
J = J + 1
End If
Next
Application.ScreenUpdating = True
MsgBox "Jobs copied to Cashflow tab"
End Sub
If you require more information, please, just let me know. I'm new here and trying to make a good impression.
I have compiled a sub that will suit your needs. The first issue I saw was your use of "On Error resume Next". This will make it nearly impossible to debug your code because the code will not tell you if there is an error it will simply skip over it. The second issue I was able to see was that you made the problem more complex than necessary. You used a For To loop where a For Each loop would get the job done more easily. I have added in a piece of code which makes the cell in the "P" column of the row with a value over 75% "Transferred" once it has been copied to the "Cashflow" sheet. The code also checks if "Transferred" is present in that column and if it is, it skips that value. Additionally, the code checks if J is 1 which would be the first value copied, and if it is not one then it adds one to the counter so that it does not paste on top of the row above.
Sub MoveRowBasedOnCellValue()
Dim QTWs As Worksheet
Dim CWs As Worksheet
Set QTWs = Worksheets("Quote Tracker")
Set CWs = Worksheets("Cashflow")
Dim xRg As Range
Dim xCell As Range
Dim I As Long
Dim J As Long
Dim K As Long
I = QTWs.UsedRange.Rows.Count
J = CWs.Cells(Rows.Count, "O").End(xlUp).Row
If J <> 1 Then
J = J + 1
End If
Set xRg = QTWs.Range("O1:O" & I)
Application.ScreenUpdating = False
For Each c In xRg
K = c.Row
If c.Value < 0.75 Then
'Do Nothing
Else
If QTWs.Cells(K, 16) <> "Transferred" Then
QTWs.Rows(K).Copy Destination:=Worksheets("Cashflow").Range("A" & J)
QTWs.Cells(K, 16).Value = "Transferred"
J = J + 1
Else
'Do Nothing
End If
End If
Next
Application.ScreenUpdating = True
MsgBox "Jobs copied to Cashflow tab"
End Sub
If you have questions about how it works, do not hesitate to let me know. Hope this helps!

Erase all the content after a specific word

I want to use VBA to 1) Find the word “Report:” in my excel sheet (I know the word “Report:” will only appear once in the sheet); 2) Erase all the content (including the word “Report”) below this cell
So, ideally, the result should look like this:
The amount of data will change, so the word “Report:” is not going to be in the row 109 every time.
This is the code I am using now,
Sub Trial()
Set myRange = ActiveDocument.Content
myRange.Find.Execute FindText:="Report:", _
Forward:=True
If myRange.Find.Found = True Then
myRange.SetRange (myRange.End + 1), ActiveDocument.Content.End
myRange.Delete
End If
End Sub
But, it gives me a
run-time error ‘424’
Give this a try:
Option Explicit
Sub ReportKiller()
Dim Report As String, r As Range, rKill As Range
Report = "Report"
Set r = Cells.Find(Report, after:=Cells(1, 1))
If Not r Is Nothing Then
Set rKill = Range(r, r.End(xlDown))
rKill.EntireRow.Delete
End If
End Sub
It will delete all sheet content from the Report cell downward.

My two macros "clash", i.e. they cannot operate simultaneously

Background
I have 2 macros in one of my worksheets that i) get the Previous Close price from Bloomberg's website straight into the worksheet [trigered by a button] (Cells H3:downwards) and ii) the other registers a timestamp if this new value causes a change in a formula located in cells K3:downwards. Then, if there is any change, the time in which it happened will be registered in the columns to the immediate right of column H.
My problem lies in that when I press the button to launch Macro i), the debugger pops "Run time error 1004. Method 'undo' of Object'_application' failed " causing Macro ii) to stop working (i.e. to stop registering the time in which there was a change in value in the column of interest). The line of code highlighted by the debugger is "Application.undo"
To be honest, I am a bit lost on the process.
This is the code
Disclaimer: Most of the comments are there to educate myself on how the code actually works. Many thanks to everyone who contributed to both Subs.
Private Sub Worksheet_Calculate()
Dim rMonitored As Range
Dim MonitoredCell As Range
Dim vSelected As Variant
Dim aNewValues As Variant
Dim ixFormulaCell As Long
On Error Resume Next
Set rMonitored = Me.Columns("K").SpecialCells(xlCellTypeFormulas)
On Error GoTo 0
If rMonitored Is Nothing Then Exit Sub 'No formula cells in column K
Application.EnableEvents = False 'Disable events to prevent infinite calc loop
Set vSelected = Selection 'Remember current selection (it may not be a range)
'Prepare the array that will store the new values, the cells those values are in, and whether or not there was a change
ReDim aNewValues(1 To rMonitored.Cells.Count, 1 To 3)
'Column1 = new value
'Column2 = cell address
'Column3 = did value change?
'Get the new value for each formula in column K
ixFormulaCell = 0
For Each MonitoredCell In rMonitored.Cells 'The formula cells may not be in a contiguous range
ixFormulaCell = ixFormulaCell + 1
aNewValues(ixFormulaCell, 1) = MonitoredCell.Value 'Store the new value
Set aNewValues(ixFormulaCell, 2) = MonitoredCell 'Store the cell address
Next MonitoredCell
Application.Undo 'This will undo the most recent change, which allows us to compare the new vs old to check for formula updates
ixFormulaCell = 0
For Each MonitoredCell In rMonitored.Cells
ixFormulaCell = ixFormulaCell + 1
'Check if the formula result is different
If MonitoredCell.Value <> aNewValues(ixFormulaCell, 1) Then
'Formula result found to be different, record that
'We can't put the timestamp in now because we still have to redo the most recent change
aNewValues(ixFormulaCell, 3) = True
End If
Next MonitoredCell
Application.Undo 'Redo the most recent change to put worksheet back in the new state
'--> THE LINE OF CODE ABOVE IS WHAT THE DEBUGGER POINTS TO
'Now that we've completed our comparison and have re-done the most recent change, check what did change and put in a timestamp in the next empty cell in same row
For ixFormulaCell = LBound(aNewValues, 1) To UBound(aNewValues, 1)
'Check for formula result change
If aNewValues(ixFormulaCell, 3) Then
'Formula result change found, get next empty cell in same row
With Me.Cells(aNewValues(ixFormulaCell, 2).Row, Me.Columns.Count).End(xlToLeft).Offset(, 1)
'Next empty cell found, put in the current datetime stamp and format it
.Value = Now
.NumberFormat = "dd-mm-yyyy, hh:mm:ss"
.HorizontalAlignment = xlGeneral
.VerticalAlignment = xlCenter
End With
End If
Next ixFormulaCell
vSelected.Select 'Re-select the remembered selection so that this operation is invisible to users
Application.EnableEvents = True 'Re-enable events so that the next calculation can be monitored for formula changes in cells of interest
End Sub
Public Sub test()
Dim re As Object, pairs(), ws As Worksheet, i As Long, s As String
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set re = CreateObject("VBScript.RegExp")
With ws
pairs = Application.Transpose(.Range("G3:G" & .Cells(.Rows.Count, "G").End(xlUp).Row).Value) ' assumes pairs start in row 3
End With
Dim results()
ReDim results(1 To UBound(pairs))
With CreateObject("MSXML2.XMLHTTP")
For i = LBound(pairs) To UBound(pairs)
.Open "GET", "https://www.bloomberg.com/quote/" & pairs(i) & ":CUR", False
.send
s = .responseText
results(i) = GetCloseValue(re, s, "previousClosingPriceOneTradingDayAgo%22%3A(.*?)%2")
Next
End With
ws.Cells(3, "I").Resize(UBound(results), 1) = Application.Transpose(results)
End Sub
Public Function GetCloseValue(ByVal re As Object, inputString As String, ByVal pattern As String) As String 'https://regex101.com/r/OAyq30/1
With re
.Global = True
.MultiLine = True
.IgnoreCase = False
.pattern = pattern
If .test(inputString) Then
GetCloseValue = .Execute(inputString)(0).SubMatches(0)
Else
GetCloseValue = "Not found"
End If
End With
End Function
Expected output
I would like both Macros to work simultaneously.

How do I fill a series using VBA?

I'm trying to get my code to search column D for cells that are not blank. When it finds one that isn't blank it copies that cell and fills the series beneath. Then I want it to repeat this code until "D3020".
However each time I run my code it takes the copied cell and continuously pastes it all the way down to "D3020". There are different values that also need to be copied so I need to fix this. I have tried using the .offset property. I have tried using .range.copy property.
Sub Fill()
Dim SRng As Range
Dim SCell As Range
Set SRng = Range("D1101:D3020")
For Each SCell In SRng
If SCell <> "" Then
SCell.Copy
Range(SCell, SCell.Offset(10, 0)).PasteSpecial(xlPasteAll)
End If
Next SCell
End Sub
I'd like this code to search Range("D1101:D3020") for cells that <> "". When one is found, fill the series beneath it, stopping at the next cell with a number in it.
For example
D1101 = 1601166 (see picture) I want to copy this and fill the series beneath it. All are exactly ten rows apart. Then D1121 = 1601168 (see picture) I want to copy/fill series for this as well.
No need for a loop; just fill the blanks with the value above.
sub fillBlanks()
dim brng as range
on error resume next
set brng = Range("D1101:D3020").specialcells(xlcelltypeblanks)
on error goto 0
if not brng is nothing then
brng.formular1c1 = "=r[-1]c"
Range("D1101:D3020") = Range("D1101:D3020").value
end if
end sub
Option Explicit
Sub Test()
FillEmptyFromTop [D1101:D3020]
End Sub
Sub FillEmptyFromTop(oRng As Range)
Dim v, a, i
With oRng.Columns(1)
a = .Value
For i = LBound(a, 1) To UBound(a, 1)
If IsEmpty(a(i, 1)) Then a(i, 1) = v Else v = a(i, 1)
Next
.Value = a
End With
End Sub

Resources