Using Named Range and Variable to Define a Range - vba

I have been trying to use a named range and variable to define a range that I want to autofill and I can only get one of the named ranges to work and not the variable in my code. The ColLetter variable is what is giving me trouble. If the I omit that variable and just put "AR" in the range the code works it is just not dynamic.
Sub test()
Dim lastRow As Long
Dim ColLetter As Range
lastRow = Range("D" & Rows.Count).End(xlUp).row 'Counts rows in specific column which controls how far to autofill.
ColLetter = Mid(ActiveCell.Address, 2, 2)
'Range("Notes_Start").AutoFill Destination:=Range("Notes_Start:AR" & lastRow) 'This works just is not dynamic. The AR for column is static.
Range("Notes_Start").AutoFill Destination:=Range("Notes_Start:ColLetter" & lastRow)
End Sub

A couple of problems, Dim ColLetter As Range should really be Dim ColLetter As String, your string concatenation is wrong, change Range("Notes_Start:ColLetter" & lastRow) to Range("Notes_Start:" & ColLetter & lastRow)

Be very careful using letters to define columns in your code.
If your active cell is A1, Mid(ActiveCell.Address, 2, 2)
yields A$, which is what you want.
If your active cell is AA1, Mid(ActiveCell.Address, 2, 2)
yields AA. You lose the $ and your reference is broken.
You would probably be better off using numerical references to columns:
Dim Col as integer
Col = ActiveCell.column
I'm not familiar with the Range("Notes_Start:..." syntax, but I believe that you could use Range("Notes_Start").Offset(lastrow,Col) to get what you're after. The Offset() may need a tweak of +1 or -1 to adjust to exactly where you need to be. Some quick trial & error will show exactly what you need to do.
You could use:
Dim adr() as string
Dim ColLetter as string
adr = split(ActiveCell.Address, "$")
ColLetter = adr(0)
But I'd still recommend using column numbers...

Related

Replace cell references with string from a corresponding cell

I have cells with calculations.
Here is one simple example, which is in row 11.
=$V11*$AB11*AF11
I'm trying to get this:
=[EAD: On Balance Sheet]*[PD Low]*[Collateral LGD High]
These 3 strings all come from row 10, in Column V, AB, and AF.
Here is another example:
Change this:
=$V11*VLOOKUP($AA11,Rates!AQ:AU,5,FALSE)*AE11
To this:
'[EAD: On Balance Sheet]*VLOOKUP([Proposed Risk Rating],Rates!AQ:AU,5,FALSE)*[Collateral LGD Low]
All formulas are on row 11, and I want to get the corresponding headers, which are all strings, from row 10.
I'm thinking that there must be a way to do this, since Excel knows all the relevant cell references, and keeps track of everything.
I can't figure out how to replace the reference with the string (in this case the corresponding header in row 10).
I'm pretty new to this so don't have enough 'reputation' to comment and clarify your question.
If the cells V11, AB11 and AF11 have the text "EAD: On Balance Sheet", "PD Low " and "Collateral LGD High" and you want this cell to show those words.
Then the following code could work:
sub combine_words()
dim i as string
dim j as string
dim k as string
i = range("V11").value
j = range("AB11").value
k = range("AF11").value
range("A11").value = "[" & i & "]*[" & j & "]*[" & k & "]"
end sub
replace the cell A11 with whichever cell you wanted the text inputed into.
Let me know if I understood your question incorrectly and I will change the code to match your needs if I can.
Perhaps a simple find and replace in the formula would work well enough. I'm sure there are lots of edge cases I'm not thinking about. Hopefully this steers the conversation in the right direction.
Sub SOExample()
Dim mySheet As Worksheet: Set mySheet = ThisWorkbook.Sheets("Sheet1")
Dim headerRng As Range: Set headerRng = mySheet.Range("A1:J1") 'Specify where to do replacements
Dim mycell As Range
Dim vkey As Variant
Dim myDict As Object: Set myDict = CreateObject("Scripting.Dictionary")
'Iterate each header row add the address as the key, and the NEXT row's Text as the value
For Each mycell In headerRng
If Not myDict.exists(mycell.Offset(1, 0).Address) Then
myDict.Add mycell.Offset(1, 0).Address, mycell.Text
End If
Next
'Iterate each cells formula and replace it
For Each mycell In headerRng
For Each vkey In myDict.keys
mycell.Offset(1, 0).Formula = Replace(mycell.Offset(1, 0).Formula, vkey, myDict(vkey), , , vbTextCompare)
Next
Next
End Sub

Copy multiple ranges (dynamic) without selecting them

QUESTION
How can I perform a copy of a list of cells, without selecting them?
Say I want to copy the ranges A1, A5 and A7.
They are stored in a string like this: addr = "A1,A5,A7"
If I select them first and copy them, the action works fine:
Range(addr).Select
Selection.Copy
When I paste from my clipboard, I only have the values I selected.
Also, if I perform a Union of Range as suggested here, it would work too without selecting:
Dim rng1 As Range, rng2 As Range, rng3 As Range, rngUnion As Range
Set rng1 = Range("A1")
Set rng2 = Range("A5")
Set rng3 = Range("A7")
Set rngUnion = Union(rng1,rng2,rng3)
rngUnion.Copy
However, I cannot neither select the ranges first, nor knowing before runtime how many ranges I will have to select.
I've tried to do this:
Range(addr).Copy
but when I perform the paste it takes all the values between A1 and A7 (basically A1:A7).
How can I get to copy the single cells without selecting them or uniting them?
BACKGROUND - not necessary to answer the question I guess
I have a listbox in which there is a list of values, that the user can multi-select (they can select like the first, the fourth, the seventh line etc.).
When they do that, I build a collection containing those values:
["value1", "value2", "value3", ... ]
Those values are unique in the spreadsheet (if I run a Find, I only can find one range).
As you can guess, I don't know in advance how many values there will be in the collection.
What I need to do is to make them copy their selection. Hence, I build a collection based on those values:
For j = 0 To Me.longList.ListCount - 1
If Me.longList.Selected(j) Then
tmpColl.Add Split(Split(Me.longList.List(j), " ")(1), " ")(0) '<-- add the story ID to the collection
End If
Next j
and then, I build the string holding the address of my multi-selection:
For j = 1 To tmpColl.Count
With Sheets("Stories list")
Set rng = .Range("A1:A10000").Find(tmpColl(j), lookAt:=xlWhole)
addr = addr & "$A$" & rng.Row & ","
End With
Next j
addr = Left(addr, Len(addr) - 1)
Something like this should work without needing to select the entries. It will split the cell addresses into an array, then iterate those to add the values into a new array for outputting to a sheet.
I've put values in A1,A3,A5, and that will move it to column B. All the code assumes this is working on the ActiveSheet.
Option Explicit
Sub SO_Example()
'Assign a string with the range you want to add
Dim addStr As String: addStr = "A1,A3,A5"
'Split the string by a comma to create an array of cells
Dim cellArr As Variant: cellArr = Split(addStr, ",")
Dim i As Long
'Resize the OutArray to be as large as the number of cells to select
Dim arrOut As Variant: ReDim arrOut(UBound(cellArr))
'Add the items to the array
For i = LBound(cellArr) To UBound(cellArr)
arrOut(i) = Range(cellArr(i)).Value
Next
'Output to column B
Range("B1:B" & i).Value = WorksheetFunction.Transpose(arrOut)
End Sub

Using Left() for variable

I'm new to VBA.
So i was trying to append prefixes to a set of values, depending on the first number of each value.
However i get a Type mismatch error on line 4. I suppose it is because I am trying to use Left() on a variable, and it is supposed to be used on a string (or something of that sorts?)
How do i accomplish that?
Thank you in advance
Sub test()
a = UsedRange.Rows.Count
Testvariable = Range("A1", "A" & a)
FirstNo = Left(Testvariable, 1)
For i= 1 To a
If FirstNo(i,1) = "1" Then
Cells(i,2) = "abc" & FirstNo(i,1)
Else Cells(i,2) = "def" & FirstNo(i,1)
End if
Next
End Sub
Trouble is you are trying to take the left of a Range object that happens to give you an array when assigned without the set keyword, when left wants a string.
This post here explains how to convert a range to a string, that you can then pass to left.
How can I convert a range to a string (VBA)?
Sub test()
dim a as long
a = UsedRange.Rows.Count
dim Testvariable as string
Testvariable = RangeToString(Range("A1", "A" & a))
dim FirstNo as string
FirstNo = Left(Testvariable, 1)
dim i as long
For i= 1 To a
If FirstNo(i,1) = "1" Then
Cells(i,2) = "abc" & FirstNo(i,1)
Else
Cells(i,2) = "def" & FirstNo(i,1)
End if
Next
End Sub
Function RangeToString(ByVal myRange as Range) as String
RangeToString = ""
If Not myRange Is Nothing Then
Dim myCell as Range
For Each myCell in myRange
RangeToString = RangeToString & "," & myCell.Value
Next myCell
'Remove extra comma
RangeToString = Right(RangeToString, Len(RangeToString) - 1)
End If
End Function
Also, be sure to always declare your variables correctly.
a = UsedRange.Rows.Count means create a variable called a of type variant and give it the value of UsedRange.Rows.Count
Testvariable = Range("A1", "A" & a) means create a variable called Testvariable of type variant and give it the .value of the range object (an array)
If these were declared properly
dim a as long
a = UsedRange.Rows.Count
dim Testvariable as string
Testvariable = Range("A1", "A" & a)
The assignment of Testvariable would have failed with a much more obvious error, informing you that you can't convert an array to a string.
Testvariable = Range("A1", "A" & a)
Testvariable is an array (unless a=1, which is not your case here)
Left is flexible enough to accept numbers in addition to strings, but NOT arrays.
If I follow your flow, I think this will get you what you are looking for.
Sub test()
Dim ws As Worksheet
Set ws = ActiveWorkbook.ActiveSheet
a = ws.UsedRange.Rows.Count
TestVariable = Range("A1", "A" & a)
For i = 1 To a
FirstNo = Left(TestVariable(i, 1), 1)
If FirstNo = "1" Then
Cells(i, 2) = "abc" & FirstNo(i, 1)
Else
Cells(i, 2) = "def" & FirstNo
End If
Next i
End Sub
I think the issue was, in addition to the other two answers, was that you need to loop through the cells and output something based on each cells value. Therefore, just throw your code just above your for loop inside and change as needed.
VBA does a lot of things behind your back, to "make things easier". The problem is that these things come back and bite you later.
Undeclared variables, for example. Without Option Explicit specified at the top of every module, VBA will happily compile code that uses variables that aren't declared. Sounds useful? It would be, if the types were inferred. But instead VBA declares an on-the-fly Variant, that holds whatever you put into it (and changes its type to accomodate whatever you're assigning).
a = UsedRange.Rows.Count
Here a is an implicit Variant/Long. Declare it as a Long integer:
Dim a As Long
Even better, give it a meaningful name:
Dim usedRows As Long
usedRows = UsedRange.Rows.Count
Now the fun part:
Testvariable = Range("A1", "A" & a)
Here Testvariable is an implicit Variant/Array. How so? The code that VBA sees looks something like this:
Testvariable = Range("A1", "A" & a).Value
And because a wouldn't be 1, that .Value is referring to more than one single cell - Excel's object model gives you an array that contains the values of all cells in the specified range, and that's what Testvariable contains.
If you wanted Testvariable to refer to a range rather than just their values, you would declare a Range object variable, and assign it with the Set keyword:
Dim testVariable As Range
Set testVariable = ActiveSheet.Range("A1:A" & a)
Notice the explicit ActiveSheet here: without it the code does exactly the same thing, so why put it in? Because you want to be as explicit as possible: unqualified Range, Cells, Rows, Columns and Names calls all implicitly refer to the active worksheet - and that's yet another source of bugs (just google up "range error 1004", you'll find hundreds of Stack Overflow questions about it).
If you meant testVariable to contain the values of every cell in that specified range, then you would declare and assign testVariable like this:
Dim testVariable As Variant
testVariable = ActiveSheet.Range("A1:A" & a).Value
And now you have an array that contains all values in range "A1:A" & a of the active worksheet: that's what your code does.. implicitly.
FirstNo = Left(Testvariable, 1)
So now we want the "first number", and we're reading it off a variant array.
The Left (or Left$) function is from the VBA.Strings module, and means to works with strings; it can work with other types too (with implicit type conversions), but if you give it an object reference or an array, it won't know how to convert it for you, and VBA will raise a run-time error.
The testVariable variant array contains Variant values: most cells will contain a Double floating-point value. Others will contain a String. Some cells can contain an error value (e.g. #N/A, #VALUE!, or #REF!) - and VBA will not be able to implicitly convert such error values either.
So before you read any cell's value, you need to make sure you can read it; use the IsError function for that:
If IsError(testVariable(1, 1)) Then
Exit Sub ' cell contains an error; cannot process
If Not IsNumeric(testVariable(1, 1)) Then
Exit Sub ' cell doesn't contain a number; cannot process
End If
' now that we KNOW our value is a numeric value...
Dim firstNumber As Double
firstNumber = testVariable(1, 1)
Notice that (1, 1)? That's because testVariable is a 1-based 2D array (without Option Base 1 implicitly sized arrays are always 0-based, except when you're getting one from a Range), so to read the value in the first row / first column, you need to read the value at index (1, 1).
But that's not what you're trying to do.
So I was trying to append prefixes to a set of values, depending on the first number of each value.
"Each value" means you need to iterate the values, so you have a loop there.
Dim i As Long
For i = 1 To a
'...
Next
It's not the "first number" we want, it's the firstDigit, of every cell we're looping over.
If FirstNo(i,1) = "1" Then
Here you've lost track of the types you're dealing with: FirstNo was assigned before the loop started, so its value will be constant at every iteration.
You mean to do this:
Dim values As Variant
values = ActiveSheet.Range("A1:A" & usedRows).Value
Dim i As Long
For i = 1 To usedRows
If Not IsError(values(i)) Then
Dim representation As String
representation = CStr(values(i))
Dim prefix As String
If Left$(representation, 1) = "1" Then
prefix = "abc"
Else
prefix = "def"
End If
ActiveSheet.Range("B" & i).Value = prefix & representation
End If
Next
Now that everything is explicit and properly indented, it's much easier to see what's going on... and now I'd seriously question why you need VBA to do that:
[B1] = IFERROR(IF(LEFT(A1, 1) = "1", "abc" & A1, "def" & A1), "")
And drag the formula down.

converting excel sumifs formula to vba code

I'm trying to do a SUMIFS calculation in VBA. It works fine when I enter it on the spreadsheet, but when I try to convert it to VBA, it doesn't seem to work.
Sheets("Master").Range("B2:B" & Range("A" & Rows.Count).End(xlUp).Row).Formula = _
"=SUMIFS(Input!C32,Input!C37,Master!C1,Input!C31,Master!R1C)"
This is the snippet of code (originally in a comment):
Dim LastRow As Long
Dim rw As Long
LastRow = Range("A" & Rows.Count).End(xlUp).Row
For rw = 2 To LastRow
Sheets("Master").Cells(rw, 2).Value = Application.WorksheetFunction.SumIfs(Sheets("Input").Range("AF:AF"), Sheets("Input").Range("AK:AK"), Sheets("Master").Range("A:A"), Sheets("Input").Range("AE:AE").Sheets("Master").Range("B2"))
Next
You aren't specifying the values you want to lookup in your criteria1. You have to specify a value, not a range.
Your sum range is fine.
Sheets("Input").Range("AF:AF")
Your criteria range1 is fine.
Sheets("Input").Range("AK:AK")
Your criteria1 needs to be a value, not a range.
Use this Sheets("Master").Range("A2").value
instead of Sheets("Master").Range("A:A")
Obviously you can replace the 2 in the criteria1 with a variable if you need to to get your loop to work.

Setting Range in For Loop

I am trying to set the range in For loop. My code works fine when I do this:
For Each i in Range("A1":"A5")
'Some process code
Next i
But I do not get the same results when I do this:
For Each i in Range("A1").End(xlDown)
'Some Process
Next i
Arent the two codes equivalent? What changes should I make to the second one that it perfoms the same way as the first one but doesn't make me hardcode the Range in the code?
The second one you have only gets the last cell in the range, which I believe would me A5 from the first example. Instead, you need to do something like this.
I structured this like a small test so you can see the first option, the corrected second, and an example of how I would prefer to do this.
Option Explicit
Sub test()
Dim r As Range
Dim x As Range
' Make sure there is stuff in Range("A1:A5")
Range("A1") = 1
Range("A2") = 2
Range("A3") = 3
Range("A4") = 4
Range("A5") = 5
' Your first option
For Each x In Range("A1:A5")
Debug.Print x.Address & ", " & x
Next
' What you need to do to get the full range
For Each x In Range("A1", Range("A1").End(xlDown))
Debug.Print x.Address & ", " & x
Next
' My preferred method
Set r = Range("A1").End(xlDown)
For Each x In Range("A1", r)
Debug.Print x.Address & ", " & x
Next
End Sub
The cleanest way to do it would probobly be to store the lastRow number in a variable like so. You can do the concatenation in the for each line:
Dim cell as range
Dim lastRow As Long
lastRow = Range("A" & Rows.Count).End(xlUp).row
For Each cell In Range("A1:A" & lastRow)
Please note that it makes a difference between using xlUp and xlDown.
xlUp gives you last cell used in column A (so you start at rows.count)
XlDown gives you last non-blank cell (you can use range("A1").End(xlDown).Row)
You'll notice a lot of people use "A65536" instead of rows.count, but 65536 is not the limit for some versions of Excel, so it's always better to use rows.count.