Should be a simple loop procedure in VBA - string

In Excel I have a column of words. I believe you call words "strings" in the programming world.
Row by row, I need to take each word in the column and put single inverted commas around it.
For example, if the word in the cell is dog, I need to change it to 'dog'.
I am trying to write a macro to do this, but I am already running into problems with the very first part of the vba code, which is just to import the column of words into vba from the excel spreadsheet.
My code is below. The Error message says "subscript out of range", but as you can see I have dimmed the array. What am I doing wrong? Thanks.
Sub putquotes()
Dim sym(1 To 162) As String
For i = 1 To 162
sym(i) = Worksheets("sheet1").Cells(i + 1, 1)
Next i
End Sub

I think your issue is your sheet1 name which should probably be Sheet1
I would use something like this which will run on the first worksheet (see Set ws = Sheets(1))
Note that the third sheet would be Set ws = Sheets(3), or you could use Set ws = Sheets("Sheet1") if you did have such a sheet
This code:
will run independent of the sheet that is selected
looks from the first to last used cell in column A (rather than hard-coding 162 rows)
uses variant arrays rather than ranges for speed
adds a double '' to ensure the first is visible :)
Sub PutQuotes()
Dim ws As Worksheet
Dim varList
Dim rng1 As Range
Dim lngCnt As Long
Set ws = Sheets(1)
Set rng1 = ws.Range(ws.[a1], ws.Cells(Rows.Count, "A").End(xlUp))
varList = rng1.Value2
For lngCnt = 1 To UBound(varList)
If Len(varList(lngCnt, 1)) > 0 Then _
varList(lngCnt, 1) = "''" & varList(lngCnt, 1) & "'"
Next
'dump updated array back over range
rng1.Value2 = varList
End Sub

You don't have a sheet named "Sheet1". Either:
This code lives in a standard module in the workbook with the data and you've renamed the
sheet, or
The code lives in another workbook and you haven't properly qualified your Worksheets property
I'm going to assume the latter. When you use collection properties like Worksheets or Cells, Excel makes assumptions on who the parent is. An unqualified Worksheets call in a standard module will assume
ActiveWorkbook.Worksheets()
An unqualified Worksheets call in the ThisWorkbook module will assume
ThisWorkbook.Worksheets()
To check where the problem is, add this line to your code
Debug.Print Worksheets("Sheet1").Parent.Name
That will tell you which workbook Excel is using and may be different than you want.
To avoid bad guessing, it's best to fully qualify your references. For instance, if you're opening the workbook with the data, it might look like this
Sub putquotes()
Dim wb As Workbook
Dim sym(1 To 162) As String
Dim i As Long
Set wb = Workbooks.Open("Path\Name")
For i = 1 To 162
sym(i) = wb.Sheets("Sheet1").Cells(i + 1, 1)
Next i
End Sub
Holding that wb reference is an easy way to qualify the reference. If you're not opening a separate file in code, you can just qualify explicitly like
ThisWorkbook.Worksheets("Sheet1")
ActiveWorkbook.Worksheets("Sheet1")
Workbooks("Mybook.xlsx").Worksheets("Sheet1")
A better way to read cell values into an array is like this
Sub putquotes()
Dim wb As Workbook
Dim sym As Variant
Dim i As Long
Set wb = Workbooks.Open("Path\Name")
sym = wb.Sheets("Sheet1").Range("A2").Resize(162, 1).Value
For i = LBound(sym, 1) To UBound(sym, 1)
Debug.Print "'" & sym(i, 1) & "'"
Next i
End Sub
That will give you a two-dimensional-base-1 array, which you may not like, but it's faster than reading them in one at a time.

I believe you want something like this...
Public Sub DoQuotes()
Dim iRow As Integer
Dim Result() As String
iRow = 1
Do While Not IsEmpty(Sheet1.Cells(iRow, 1))
ReDim Preserve Result(iRow - 1)
Result(iRow - 1) = "'" & Sheet1.Cells(iRow, 1) & "'"
iRow = iRow + 1
Loop
For Each x In Result
MsgBox (x)
Next x
End Sub
However, bear in mind that Excel will treat the first quote as a text delimiter so it whilst the value in the array is 'something' it will look like something' in Excel.
Just a general aside point, try to avoid calls to Worksheets() instead use the strongly typed Sheet1 object - saves all sorts of future pain if the worksheets get renamed. You can see what the sheets are "really" called in the vba editor. It will say something like Sheet1(MyWorksheet)

Related

How can i write operation in vba?

Hello everyone i'm doing a macros code with vba, and i would like asking something, how can i write the next operation through vba macro.
Suppose you have a sheet with two columns one called "C" and the other "D" and each cell from this column has the next operation:
ws.Range("D1") = 0
ws.Range("D2") = ws.Range(C2)- ws.Range(C1)
ws.Range("D3") = ws.Range(C3)- ws.Range(C2)
...
ws.Range(Di+1) = ws.Range(Ci+1) -ws.Range(Ci)
How can i write in vba syntax an operation like:
ws.Range("D:D").FormulaR1C1 = "= R[i+1]C[""C""] - R[i]C[""C""]"
Thank you for your helping.
There are many options. See example of the code below (assuming I understood what you are after correctly)
Sub FillCells()
Dim RangeToFill As Range
Dim CurCell As Range
Dim wks As Worksheet
Set wks = ActiveSheet
Set RangeToFill = wks.Range("D2:D8") ''' define the range as required or even better - use named ranges in the so
'''' Option 1
''' youcan use R1C1 reference style for the whole range - very fast and nice solution
RangeToFill.FormulaR1C1 = "=RC[1]-R[-1]C[1]"
'''' Option 2
''' or you can use .Offset property of the range object. Note that .Address(0,0) has two zeros for the cell address not to be frozen,
''' i.e. not =$E$2 - $E$1 but =E2-E1
''' This also works but could be slower on big ranges and formula looks pretty ugly to my taste
For Each CurCell In RangeToFill.Cells
CurCell.Formula = "=" & CurCell.Offset(0, 1).Address(0, 0) & "-" & CurCell.Offset(-1, 1).Address(0, 0)
Next CurCell
End Sub

Load Values From Closed Workbook Into Array

I have not used VBA in a while so I'm a bit rusty. Seeking help.
The task:
I need to pull all unique value from a given range in a closed workbook, into my sub's workbook.
I'm thinking of calling a function that returns an array of my unique non-blank values. because I need to know the quantity of unique values to insert the right amount of lines in the main workbook. Can't juts copy-paste the values. And also because I need to remove the duplicates.
Some relevant code bits up to know:
Sub PullACParts()
Dim FullFilePath As String
Dim arrPartList() As String
FullFilePath = "C:\Users\[...]file1.xlsx"
arrPartList() = GetValues(FullFilePath)
and that calls:
Function GetValues(path as string) as Variant
Dim arrValues() As String
Dim arrUnikVals As String
On Error GoTo ErrHandler
Application.ScreenUpdating = False
Dim src As Workbook
' OPEN THE SOURCE EXCEL WORKBOOK IN "READ ONLY MODE".
Set src = Workbooks.Open(FullFilePath, True, True)
' GET THE TOTAL ROWS FROM THE SOURCE WORKBOOK.
Dim iTotalRows As Integer
TotalRows = src.Worksheets("Sheet1").Range("D1:D" & Cells(Rows.Count, "D").End(xlUp).Row).Rows.Count
' COPY NON-BLANK DATA FROM SOURCE FILE COLUMN-4 TO ARRAY.
Dim iCnt As Integer ' COUNTER.
Dim ArrDim As Long: ArrDim = 0
For iCnt = 4 To TotalRows
If src.Worksheets("Sheet1").Range("D" & iCnt).Value <> "" Then
arrValues(ArrDim) = src.Worksheets("Sheet1").Cells(4 & iCnt).Formula 'FAILS HERE
ArrDim = ArrDim + 1
End If
Next iCnt
' CLOSE THE SOURCE FILE.
src.Close False ' FALSE - DON'T SAVE THE SOURCE FILE.
Set src = Nothing
' Remove Duplicates
arrUnikVals = RemoveDupesColl(arrValues)
GetValues = arrUnikVals
ErrHandler:
Application.EnableEvents = True
Application.ScreenUpdating = True
End Function
It fails when I try loading the array. Says "arrValues(ArrDim) =
I tried a few different things but just can't figure it out.
Thanks in advance for the help!!
You don't need to loop this. First set your variable types.
Dim arrValue as Variant, lr as long
Once this is a variant you can simply grab the array from the range in one go by transposing it:
lr = src.Worksheets("Sheet1").range("D" & rows.count).end(xlup).row
Application.transpose(src.Worksheets("Sheet1").range("D4:D" & lr))
If you need to loop the values in the array later you simply do something like this:
Dim X as long
For X = lbound(arrValue) to ubound(arrValue)
msgbox arrValue(X)
next
I do however note in your question you say you want to pull data from a CLOSED workbook but your code is opening the book. There are ways to pull data direct from a closed book but it would be difficult because you don't know how many rows it will be without opening the book. If you absolutely have to leave the book closed then post back.
I also note you are using cells(4 & iCnt) to try and populate the array, you do know that is just the cell counting from left to right then row to row? in other words cells(2) is B1 and cells(16386) is B2. You should use cells(Row,Column) I think from what you have said you want to increment the row but if not then post back, we simply double transpose if it's across columns instead of rows.

Perform Action IF the active sheet is one of a group of sheets

I'm trying to clear rows in a worksheet.
I found ActiveSheet.Range(range).Clear.
I have similar looking worksheets in the workbook and I am trying to protect myself from accidentally deleting stuff I need.
I am trying to check if the active sheet is one of 7 worksheets in the workbook. If it isn't, don't clear.
If Not ActiveSheet.Name = Worksheets("014").Name Then
Else
ActiveSheet.Range("40:42").Clear
End If
This works for a single worksheet, but I need to check for 6 more sheet names. I can code this with multiple "IF NOT" statements checking for each sheet, but is there a shorter, cleaner way to check all seven worksheet names in the same "IF NOT" statement?
I would use a Scripting.Dictionary to hold the names of the sheets you are interested in checking against:
Dim dict As New Scripting.Dictionary
dict.Add("Sheet1", 1) 'the actual values don't matter; we want to check against the keys
dict.Add("Sheet2", 1)
dict.Add("Sheet3", 1)
dict.Add("014", 1)
Then we can check if the key exists in the dictionary:
If dict.Exists(ws.Name) Then
ws.Range("40:42").Clear
End If
You could also use an array:
Dim sheetNames(4) As String
sheetNames(0) = "Sheet1"
sheetNames(1) = "Sheet2"
sheetNames(2) = "Sheet3"
sheetNames(3) = "014"
but since there's no built-in way to check for the existence of an item in the array, you have to write such a function yourself:
Function ContainsItemm(col As Variant, item As Variant) As Boolean
Dim x As Variant
For Each x In col
If x = item Then
ContainsItem = True
Exit Function
End If
Next
End Function
and use it thus:
If ContainsItem(sheetNames, ws.Name) Then
ws.Range("40:42").Clear
End If
Put all of the sheet names into one string and then use that.
Dim strSheetNames as String
Dim ws As Worksheet
Set ws = ActiveSheet
strSheetNames = ",Sheet1,Sheet2,Sheet3,014..," 'include all sheet names
If InStr(strSheetNames,"," & ws.Name & ",") <> 0 Then
ws.Range("40:42").Clear
End If
The comma searching trick is per Zev Spitz.

Preserve Array of ActiveSheets as Variable

I am hoping someone can assist me with a sheet array issue I am having. For background information, the main "template" sheet is copied multiple times as a new input is stored in each version. The newly created sheet is named after the input. The inputs are almost random, so defining by sheetname is not an option.
Once the workbook has all of the new sheets added I am trying to isolate a subset of the sheets. The problem I run into is the sheet numbers (as seen in the project window) don't necessary go in order. Also many sheets are hidden.
The following code is portion being used to create the sheet array, which breaks upon trying to save the array as a variable (objsheets).
Not sure what I am missing to have this array saved. Any help would be greatly appreciated. Code Below.
Thanks,
JM
At this point the workbook has the "template" sheet copied and has 50 new sheets added (hypothetical number).
Sub SheetArrayTest
Dim mySheet As Object
Dim objShts As Excel.Sheets
Dim varArray As Variant
Dim FirstSheetNum As Long
Dim FirstSheet As String
Dim LastSheetNum As Long
Dim LastSheet As String
'Selects template sheet
Sheets("Template").Select
'Selects the first sheet following the template sheet, and is the desired start of the array
Sheets(ActiveSheet.Index + 1).Activate
'Creates variables for starting point
FirstSheet = ActiveSheet.Name
FirstSheetNum = ActiveSheet.Index
'Loops through each sheet in the workbook following the "FirstSheet" and selects it to create the array
For Each mySheet In Sheets
With mySheet
If .Visible = True And mySheet.Index >= FirstSheetNum Then .Select Replace:=False
End With
LastSheetNum = mySheet.Index
LastSheet = Sheets(LastSheetNum).Name
If FirstSheetNum < LastSheetNum Then
'Attempt at preserving the array
ReDim varArray(FirstSheetNum To LastSheetNum)
varArray(LastSheetNum) = LastSheet
End If
Next mySheet
'ERROR
Set objShts = Sheets(varArry)
...
End Sub
You can't use the Set keyword to assign to an array. That's your first problem, and would explain an error on that line.
Set objSheets = Sheets(varArray)
That line may also fail because the Sheets takes an index value, not an array of values.
You're also not preserving the array with ReDim Preserve to extend it.
In any case... let's see if we can't figure it out. it sounds like you're trying to store an array of Sheet/Worksheet Objects. But your code is assigning a string value to your array (LastSheet), rather than an object.
Instead of storing the sheet name (LastSheet) in the array, store the sheet itself (unless you really need the index value).
You can maybe modify this:
Dim numberOfSheets as Integer
numberOfSheets = -1
For Each mySheet In Sheets
With mySheet
If .Visible = True And mySheet.Index >= FirstSheetNum Then .Select Replace:=False
End With
LastSheetNum = mySheet.Index
LastSheet = Sheets(LastSheetNum).Name
If FirstSheetNum < LastSheetNum Then
'increase the size of the array
numberOfSheets = numberOfSheets + 1
ReDim Preserve varArray(numberOfSheets)
Set varArray(numberOfSheets) = Sheets(LastSheet)
End If
Next mySheet
You do not need the variable objSheets at all.
Sub M_snb()
For Each sh In Sheets
If sh.Visible = -1 And sh.Name <> "template" Then c00 = c00 & "|" & sh.Name
Next
Sheets(Split(Mid(c00, 2), "|")).Select
End Sub
Sub M_snb()
For Each sh In Sheets
If sh.Visible = -1 And sh.Name <> "template" Then c00 = c00 & "|" & sh.Name
Next
Sheets(Split(Mid(c00, 2), "|")).Select
End Sub
courtesy of snb

excel vba how to copy the value of multiple non-contiguous ranges into an array

I am trying to copy the value of multiple non-contiguous ranges into an array. I wrote code like this:
summaryTempArray = .range("A2:D9,A11:D12,A14:D15").Value
But it copies only the first part (A2:D9). Then, I tried the following and I get the error - "Method Union of Object _Global Failed" - is there any mistake in the way that I am using union?
summaryTempArray = Union(.range("A2:D9"), .range("A11:D12"), .range("A14:D15")).Value
Don't know what was wrong with your union, but it would have created the same range, which you stated in your first attempt.
The problem is, you have now multiple areas. Which you can, and as far as I know, has to address now.
Here is an example, which will resolve in an array of all areas, without adding each cell individually, but adding each area individually to the summary array:
Public Sub demo()
Dim summaryTempArray() As Variant
Dim i As Long
With Tabelle1
ReDim summaryTempArray(1 To .Range("A2:D9,A11:D12,A14:D15").Areas.Count)
For i = 1 To .Range("A2:D9,A11:D12,A14:D15").Areas.Count
summaryTempArray(i) = .Range("A2:D9,A11:D12,A14:D15").Areas(i)
Next i
End With
End Sub
Hope this helps.
I believe Jook's solution is as good as you are going to get if it is important to get the source ranges into an array. However, I think the solution should include instructions on extracting values from a ragged array. This is not difficult but the syntax is obscure.
I cannot get your Union statement to fail either. I assume there is something about the context that causes the failure which I cannot duplicate.
The code below shows that the two ranges are the same and that only the first sub-range is loaded to an array as you reported. It finishes with an alternative approach that might be satisfactory.
Option Explicit
Sub Test()
Dim CellValue() As Variant
Dim rng As Range
With Worksheets("Sheet1")
Set rng = .Range("A2:D9,A11:D12,A14:D15")
Debug.Print rng.Address
Set rng = Union(.Range("A2:D9"), .Range("A11:D12"), .Range("A14:D15"))
Debug.Print rng.Address
' The above debug statements show the two ranges are the same.
Debug.Print "Row count " & rng.Rows.Count
Debug.Print "Col count " & rng.Columns.Count
' These debug statements show that only the first sub-range is included the
' range counts.
CellValue = rng.Value
Debug.Print "Rows " & LBound(CellValue, 1) & " to " & UBound(CellValue, 1)
Debug.Print "Cols " & LBound(CellValue, 2) & " to " & UBound(CellValue, 2)
' As you reported only the first range is copied to the array.
rng.Copy Destination:=Worksheets("Sheet2").Range("A1")
' This shows you can copy the selected sub-ranges. If you can copy the
' required data straight to the desired destination, this might be a
' solution.
End With
End Sub
I had the same problem & tried a few methods without success until I hit on this:-
dim i as integer
Dim rng1 as range
Dim str as string
dim cels() as string
Set rng1 = sheet1.Range("A2:D9,A11:D12,A14:D15")
str = rng1.address(0,0)
cels() = split(str, ",") '<--- seems to work OK
for i = 0 to 2
Debug.Print cels(i)
Next i
I would be interested if this is an "incorrect" conversion method.
It is possible to create a multi dimensional array from non concurrent cell ranges. What I did was use a bit of the code above for the range copy mechanic I learned 2 things; that with that method you can refer to the actual cells and not just the data and you can also move and preserve order with it. In my personal project we have to use some excel files to fill out calibration data. It runs the calculations and produces a report of calibration record for our files to refer to later. These stock files are boring! I wanted to spruce it up a bit and color most of the documents empty cells depending on if the calibration passed or not. The files separate the individual check steps so the ranges I wanted to look through were not always adjacent. What I came up with is to use the copy function below to create a new sheet and paste all the non-concurrent ranges into one nice new set of concurrent ones and then have my array look at the new sheet to draw my table. I have it run the lookup I needed and then get rid of the now useless sheet.
Public Sub ColorMeCrazy()
' First Declare your variables that you will need line notes will be added to all the ones for just the array problem
Dim chkarray As Variant
Dim i As Integer ' for the array lookup loop
Dim j As Integer ' also for the array lookup loop
Dim chk1 As Boolean
Dim chk2 As Boolean
Dim cpyrange As Range ' the non-concurrent range collector haha.
Dim cz As Range
chk2 = True
Set cz = Worksheets("AN_PRM-36").Range("A1:I1,C2:I2,I3:I35,A30:H32,D33:H35,C34:C35,A36:E36,A22:H23,D24:H24,A16:H16,A8:H9,D10:H10")
' the next item below sets the ranges i wish to use. see that they arent all just right next to eachother.
Set cpyrange = Worksheets("AN_PRM-36").Range("G7:H7,G15:H15,G21:H21,G28:H29")
' this is the new sheet i made to create the array with
Sheets.Add.Name = "AN_PRM-36tmp"
' the data gets coppied to the new sheet but now its all together
cpyrange.Copy Destination:=Worksheets("AN_PRM-36tmp").Range("A1")
' now i tell the array i want it to use the data on the new sheet
chkarray = Worksheets("AN_PRM-36tmp").Range("A1:B5")
'this was my look up for the nonsense that i wanted to do later
For i = LBound(chkarray, 1) To UBound(chkarray, 1)
For j = LBound(chkarray, 2) To UBound(chkarray, 2)
Debug.Print chkarray(i, j)
If chkarray(i, j) = "Pass" Then
chk1 = True
Else
chk2 = False
End If
Next
Next
If chk1 = True And chk2 = True Then
cz.Interior.ColorIndex = 4
Else
cz.Interior.ColorIndex = 3
End If
' this last bit will get rid of the new sheet and not ask you are you sure you want it gone.
Application.DisplayAlerts = False
Sheets("AN_PRM-36tmp").Delete
Application.DisplayAlerts = True
End Sub

Resources