Why my vba script changes all hyperlinks together instead individually - vba

first of all I'm a not good at vba, I used many tuts, but It's not what I want ;)
What I'm trying to accomplish:
Select range of hyperlinks in spreadsheet and set hyperlinks to call another spreadsheet cells (always) from A2 to AX (depends on how many rows I selected).
(Sorry for not proper naming, last time I used vba was about 10y ago)
Before run a script: all hyperlinks are set to different spreadsheet to call cell A2, like this: CommLinkItem_57!A2
Important: it can't be used =HYPERLINK(cell;name) function, couse another script is using this spreadsheet and It not work with this function
After run a script: hyperlinks are not incremented from A2 to AX, instead all hyperlinks (event those that I not selected) are calling last iterated element witch is AX
Sub LoopSelection()
Dim cel As Range
Dim selectedRange As Range
Dim aa As String
Dim counter As Integer
counter = 2
Set selectedRange = Application.Selection
For Each cel In selectedRange.Cells
Debug.Print cel.Address & " " & cel.Hyperlinks.Count
If cel.Hyperlinks.Count > 0 Then
aa = cel.Hyperlinks.Item(1).SubAddress
If cel.Hyperlinks.Item(1).SubAddress Like "*!*" Then
cel.Hyperlinks.Item(1).SubAddress = Trim(Split(aa, "!")(0)) & "!A" & counter
End If
counter = counter + 1
Debug.Print cel.Hyperlinks.Item(1).SubAddress
End If
Next cel
End Sub
For example i select 10 cells form I10 to I20 and then I run a script..
My output in console is like this:
$I$10 1
CommLinkItem_57!A2
$I$11 1
CommLinkItem_57!A3
$I$12 1
CommLinkItem_57!A4
$I$13 1
CommLinkItem_57!A5
$I$14 1
CommLinkItem_57!A6
$I$15 1
CommLinkItem_57!A7
$I$16 1
CommLinkItem_57!A8
$I$17 1
CommLinkItem_57!A9
$I$18 1
CommLinkItem_57!A10
$I$19 1
CommLinkItem_57!A11
$I$20 1
CommLinkItem_57!A12
(works fine, finds proper cells (I10:I20), finds one hyperlink, finds spreadsheet named CommLinkItem_57 and set (in console output) proper incremented cell value from A2 to A12
So in excel cell I10 and I20 are calling CommLinkItem_57!A12.
And that's a problem..
Can you point where I made mistake, and how to fix that problem

Your code is OK. The problem is that worksheets maintain a HyperLinks collection of distinct URLs. I suspect your initial URLs are all the same, hence you're always updating the same HyperLink and end up with the one with the highest counter value. If possible, make your initial URLs distinct.

From what I see the counter should be out of the condition. Like this:
For Each cel In selectedRange.Cells
counter = counter + 1
Debug.Print cel.Address & " " & cel.Hyperlinks.Count
If cel.Hyperlinks.Count > 0 Then
aa = cel.Hyperlinks.Item(1).SubAddress
If cel.Hyperlinks.Item(1).SubAddress Like "*!*" Then
cel.Hyperlinks.Item(1).SubAddress = Trim(Split(aa, "!")(0)) & "!A" & counter
End If
Debug.Print cel.Hyperlinks.Item(1).SubAddress
End If
'or put the counter here, it depends on your code...
Next cel

Like #Excelosaurus said, all hyperlinks were reference like, and when I changed one, all were changed too. So I make workaround and create hyperlinks from basics:
I'm counting from A2 to AX so counter is set to 2
Name of table where nested cells are always is in the same column in index 2, so table name sets row 2, and column of a selected range and takes value of cell i.e. tableName
Hyperlinks are created only in active sheet, line: With Worksheets(Application.ActiveSheet.Index)
If we don't want address to url or file, make Address property, i.e. empty quote ""
I think rest is self-explanatory in code:
Sub LoopSelection()
Dim selectedRange As Range
Dim counter As Integer
Dim tableName As String
counter = 2
Set selectedRange = Application.Selection
tableName = Cells(2, selectedRange.Column).Value
For Each cel In selectedRange.Cells
With Worksheets(Application.ActiveSheet.Index)
.Hyperlinks.Add Anchor:=.Range(cel.Address), _
Address:="", _
SubAddress:=tableName & "!A" & counter, _
TextToDisplay:=tableName
End With
counter = counter + 1
Next cel
End Sub

Related

VBA Excel Format Range when value is found

I'm trying to implement a macro that looks for the words "TRUE" and "FALSE" in a huge array of data - and then, when found, changes the color of the cells above it.
Specifically, I would like it to color not the TRUE/FALSE-cell, but the 30 cells directly above it. This is where things get tricky... I hope someone can help.
I've tried adapting the below code, but mostly I'm adding it as inspiration at this point.
Sub ChangeColor()
lRow = Range("C" & Rows.Count).End(xlUp).Row
Set MR = Range("C2:C" & lRow)
For Each cell In MR
Select Case cell.Value
Case "Yes"
cell_colour = 4
Case "y"
cell_colour = 4
Case Else
cell_colour = 3
End Select
cell.Interior.ColorIndex = cell_colour
Next
End Sub
Using a datafield array
Looping through a range is always time consuming; this should speed it up.
Caveat: Formatting single cells can maximize file size, so at least I reformat the whole column C to xlColorIndexNone.
Option Explicit
Public Sub Mark30CellsAbove()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("MySheet")
Dim v As Variant
Dim i As Long, j As Long, n As Long, m As Long, r As Long
Dim Rng As Range
Dim t As Double
' stop watch
t = Timer
' get last row in column C
n = ws.Range("C" & ws.Rows.Count).End(xlUp).Row
' get values to one based 2dim array
v = ws.Range("C1:C" & n).Value
' clear existing colors over the WHOLE column to minimize file size
ws.Range("C:C").Interior.ColorIndex = xlColorIndexNone
' loop through C2:Cn and mark 30 rows before found condition
For i = 2 To n
' check condition, find string "true" or "false"
If InStr(".true.false.", "." & LCase(v(i, 1)) & ".") > 0 Then
' set range block - fixed rows count 30 above found cell
If i < 32 Then ' only in case of less than 30 rows
Set rng = ws.Range("C2:C" & (i - 1))
Else
Set rng = ws.Range("C" & (i - 30) & ":C" & (i - 1))
End If
rng.Interior.ColorIndex = 4
End If
Next i
MsgBox "Time needed: " & Format(Timer - t, "0.00") & " seconds."
End Sub
Of course you could also loop within If - EndIf, just to see this slower method:
If InStr(".true.false.", "." & LCase(v(i, 1)) & ".") > 0 Then
' Always avoid to loop through a range
' For j = i - 1 To i - 30 Step -1
' If j < 2 Then Exit For ' optional escape if one line under title row
' ws.Cells(j, 3).Interior.ColorIndex = 4
' Next
End If
The code that I posted should only highlight cells in column B whose value is different from the corresponding cell in column A. I tested it and it worked OK.
If you want to try conditional formatting:
Select column B, or the part of column B that you want to colour conditionally.
In the following, I will assume that B1 is the active cell within the selection.
On the Home tab of the ribbon, click Conditional Formatting > New Rule...
Select "Use a formula to determine which cells to format".
Enter the formula =B1<>A1
If the active cell is not in row 1, adjust the formula accordingly. For example, if the active cell within the selection is B3, use =B3<>A3
Click Format...
Activate the Fill tab.
Select the desired fill colour.
Click OK until all dialogs have closed.
Change some values in column A and/or B to see the result.
Refer - https://social.technet.microsoft.com/Forums/ie/en-US/2fffa4d8-bbba-473b-9346-5fce8f0728a8/using-vba-to-change-a-cell-colour-based-on-the-information-in-a-different-cell-excel-2010?forum=excel
First you need to check whether the row of the cell is higher than 30 and then it you can offset to change the color:
Thus instead of this line: cell.Interior.ColorIndex = cell_colour
write this:
If cell.Row > 30 Then cell.Offset(-30, 0).Interior.ColorIndex = cell_colour
This may be done without VBA. You should set up two conditional formatting with formulas. First:
=COUNTIF(OFFSET(INDIRECT(ADDRESS(ROW(), COLUMN())),1,0,29,1), "TRUE")>0
and the same for false. To highlight the cell you just need to use Highlight Cell Rules (the top option for CF).
I would do this with conditional formatting
Mark all your data and press "Conditional Formatting". Enter 2 rules with Use a formula...
First rule is for TRUE. Assuming that you start with Col A:
=COUNTIF(A2:A31;TRUE)
The second rule is similar, just exchange TRUE by FALSE. Below the formula, press the "Format" button to set the color.
Explanation:
I reverted the logic: Instead of searching for TRUE/FALSE and then format the cells above, I look for every cell if it has at least one entry TRUE resp. FALSE in the next 30 cells. However, I have to admit I don't know how fast Excel can handle such a large amount of data.

How can you detect text entry throughout multiple sheets and manipulate cells below it?

I am trying to figure out how to add some cell values together from different sheets but I don't know what the cells references are as they vary!
Basically the values i need will appear 2 rows below some certain text. So I was looking for a formula that searches multiple sheets, finds the specific text, goes 2 rows below then adds the values together.
Here's something I hope you can adapt to your situation by changing the sheet and row and column range, the text to look for, and the destination of the total.
Sub findfvalues()
Dim rowValue
Dim total
total = 0
For r = 1 To 25 'update this to suit your needs
For c = 1 To 25 'update this to suit your needs
If Cells(r, c).Value = "f" Then 'update "f" to search for what you want
rowValue = r + 2
total = total + Cells(rowValue, c).Value
End If
Next
Next
Cells(30, 1).Value = total 'update this to suit your needs
End Sub
So we just check every cell for the "f" and if we find it, we add the value to a running total. Display the total at the end.
This will look in each worksheet, and if your text is found, add the value that's two rows below to a running total:
Sub find_Values()
Dim ws As Worksheet
Dim findStr As String
Dim foundCell As Range
Dim total As Long
findStr = "my Text"
For Each ws In ActiveWorkbook.Worksheets
Set foundCell = ws.Cells.Find(what:=findStr)
If Not foundCell Is Nothing Then
total = total + foundCell.Offset(2, 0).Value
End If
Next ws
Debug.Print "The value is: " & total
End Sub

Excel macro help - If statement with a variable range

I am creating a macro to help organize a data dump (sheet 1) into an invoice (sheet 2). I have coded most of the macro, but am stuck on the following.
I want the macro to read column Y on sheet 1, which is a variable range (can be 2 rows to 50) and check if it says "CB". If this is true, then E11 on sheet 2 is Yes, otherwise No, and so on until it reaches the end of column Y on sheet 1.
I have the following:
Sheets("Data_Dump").Select
intCounter = 1
While Range("Y" & (intCounter + 1)) <> ""
intCounter = intCounter + 1
Wend
intCardSize = intCounter
MsgBox (intCardSize)
Sheets("Data_Dump").Select
If Range("Y" & intCardSize) = "CB" Then
Sheets("Reconciliation").Select
Range("E11:E" & intCardSize).Select
Range("E11") = "Yes"
End If
The while range seems to work and it displays the number of cells with text in column Y, but I can't seem to wrap my head around how to get it to move from Y1 to Y2 and so on and then paste the response into E11 then E12 and so on.
The problem that you are having is that your code doesn't loop to try to compare. The While loop that you have only looks to see if there is something in the next cell. In fact, it actually skips the first row, but maybe that was intentional.
Dim dataSheet As WorkSheet
Dim recSheet As Worksheet
Dim lngCounter As Long 'Use long because an integer may not be big enough for large dataset.
Dim intCardSize As Long
Set dataSheet = ThisWorkbook.Sheets("Data_Dump")
Set recSheet = ThisWorkbook.Sheets("Reconciliation")
'You want to set the sheets to a variable instead of referring to the whole path each time
'Also, Note the usage of "ThisWorkbook" which guarantees the worksheet
'is coming from the one with code in it.
lngCounter = 2 'If you want to start looking at row 2, start at row 2 with
'the variable instead of starting the variable and checking var+1
While dataSheet.Range("Y" & (lngCounter)) <> ""
'While there is a value in the column
'intCardSize = intCounter 'Not sure what this is supposed to do
'MsgBox (intCardSize) 'This looks like debugging. Commenting out.
If dataSheet.Range("Y" & lngCounter) = "CB" Then
'Check each row as you go through the loop.
'Sheets("Reconciliation").Select
'Avoid selecting sheet/range. Unneccessary work for computer.
recSheet.Range("E" & (9 + lngCounter)) = "Yes"
'Set reconciliation sheet value to "Yes" if data sheet has "CB"
'The reconciliation sheet starts on row 11, whereas the datasheet
'starts at row 2 ,a difference of 9
Else
recSheet.Range("E" & (9 + lngCounter)) = "No"
'Otherwise set to no.
End If
lngCounter = lngCounter + 1
Wend
intCardSize = lngCounter - 1 'It's been increased to one past the last item.
MsgBox intCardSize 'Display the last row checked.
I hope I understood your code goal as follows
With Sheets("Data_Dump")
With Sheets("Reconciliation").Range("E11").Resize(.Cells(.Rows.Count,1).Row)
.Formula="=IF('Data_Dump'!Y1="CB", "Yes","")"
.Value= .Value
End With
End With

VBA complex vlookup between worksheets to get average of relative cells

I have a workbook with 2 worksheets. On Sheet1 is a list of names in ColC, and on Sheet2 in column C is the same list of names, but spaced out with data in Column D relating to each name almost as a heading. i.e.
Ben 678
700
450
200
Janet 9
23
So I need a vlookup function to Look up the name in ColC Sheet1, and then find the corresponding name in ColC Sheet2, and do an average of the values for that name in ColD until the value in ColC changes (and the next name appears). The number of values in ColD per name changes between 1 and 100 so theres no set range.
(I'm looking for a solution to calculate the average of the last 6 values per name before the next appears - but I can try to modify that later on by myself once I have a structure.)
I am familiar with VBA but no expert, and this is just beyond my ability - I have tried a few things for a few hours and no luck. I have also this code that does a similar thing (I found it on a forum) but only pastes one value and I am not able to modify it enough to suit my needs - it uses VBA to put formulas in specific cells. (it's pretty useless but I thought it was a start)
Sub MCInternet()
'CODE OFF WEB FOR RETURNING VALUE IN COL ... AFTER A LOOKUP OF VALUE IN RANGE - DOESNT ADDRESS RANGE JUST SINGLE CELL
Dim Cll As Range
Dim lngLastRow As Long
lngLastRow = Cells(rows.count, "C:C").End(xlUp).Row
'Sheets("Unpaid List").Range("H2:H" & lngLastRow).ClearContents
For Each Cll In Sheets("Sheet2").Range("C1:C" & Sheets("Sheet2").Range("C1").End(xlDown).Row)
'Cll.Offset(, 6).Formula = "=Vlookup(" & Cll.Address & ", " & Sheets("Sheet1").Name & "!A:C,1,False)"
Cll.Offset(, 6).Formula = "=Vlookup(" & Cll.Address & ", " & Sheets(Sheets.count).Name & "!A:C,1,False)"
Next Cll
End Sub
I think it's better to define in a new module a Public Function like:
Public Function FindP(xx As Range) As Long
Application.Volatile
Dim FoundIndex
Dim SumFound, i As Long
Set FoundIndex = Sheets("Sheet2").Range("C:C").Find(xx.Value)
If (FoundIndex Is Nothing) = True Then
FindP = 0
Exit Function
Else
SumFound = 0
For i = 0 To 100
If (FoundIndex.Offset(i, 0) = "") Or (FoundIndex.Offset(i, 0) = xx.Value) Then
SumFound = SumFound + FoundIndex.Offset(i, 1).Value
Else
Exit For
End If
Next
FindP = SumFound
End If
End Function
and in every cells in the sheet1:
D1 -> =FindP(C1)
and autocomplete.
The function search in the column C of the sheet2 the name, after loop to sum every value if the name in column C it's equal (1st line) or empty (2nd ... n line).

Excel VBA for loop a Named List

I have a spreadsheet with a column of data day of the week and using a macro to execute a VBA. Column A is the day of the week and Column B is the name of the object. When I run the macro, it runs a For loop through a Named List and will populate the items in a calendar on another sheet. The macro works fine as long as I have the Named List in a fixed length (ie $L2:$A14) so if I add new data, I would need to fix the Named List.
Sub UpdateCalendar()
i = 2
Dim strRngName As String
lngLast = Sheets("Servers").Range("B" & Rows.Count).End(xlUp).Row
For Each c In Application.Range("ScheduledDates")
strRngName = c.Text
strUser = c.Offset(0, -1).Value
User = c.Offset(0, -10).Value
If (i > 45) Then
<code stuff>
i = i + 1
Next
End Sub
I tried switching line 5 to something like this:
For Each c In Sheets("Servers").Range("L" & Rows.Count).End(x1Up).Row
but it doesn't like that (I'm guessing it doesn't see it as a full array?). The problem with the way this executes is if the "ScheduledDates" field is blank, it will throw an error and stop the script, thus I'm using a fixed length in my Named List. Not sure if there's any way around this.
First, dim c as range, then update your code to:
For Each c In Sheets("Servers").Range("L2:L" & Sheets("Servers").cells(Rows.Count,"L").End(xlUp).Row).cells
or
dim c as range, lLastRow as long
lLastRow=Sheets("Servers").cells(Rows.Count,"L").End(xlUp).Row
For Each c In Sheets("Servers").Range("L2:L" & lLastRow).cells
You can also update the definition of your named range so it becomes a dynamic named range, either using an =offset( / counta structure, of by referencing a listObject
Assuming that column B always has an entry, I prefer this approach:
Sub UpdateCalendar()
Dim rng as Range
Dim strRngName As String
Set rng as Sheets("Servers").Range("B2")
While rng <> ""
strRngName = rng.Text
strUser = rng.Offset(0, -1).Value
'!!!Below line will cause an error in your code as B2 offset by -10 would be B-8!!!
User = rng.Offset(0, -10).Value
If (rng.Row > 45) Then
'<code stuff>
Set rng = rng.Offset(1)
Wend
End Sub
You can use your original code by making the named range dynamic.
For Example, entering the below formula in the 'Refers To' field of the named range selects a range from A2:C where is the row number of the last filled row.
=OFFSET(Sheet1!$A$1,1,0,COUNTA(Sheet1!$A:$A)-1,3)
(assuming data extends from col A to col C with headers in row1)