Excel VBA array of Selected Range - vba

I know how to make two functions on each column (in this case TRIM and STRCONV to ProperCase
Dim arrData() As Variant
Dim arrReturnData() As Variant
Dim rng As Excel.Range
Dim lRows As Long
Dim lCols As Long
Dim i As Long, j As Long
Range("H2", Range("H2").End(xlDown)).Select
lRows = Selection.Rows.Count
lCols = Selection.Columns.Count
ReDim arrData(1 To lRows, 1 To lCols)
ReDim arrReturnData(1 To lRows, 1 To lCols)
Set rng = Selection
arrData = rng.Value
For j = 1 To lCols
For i = 1 To lRow
arrReturnData(i, j) = StrConv(Trim(arrData(i, j)), vbProperCase)
Next i
Next j
rng.Value = arrReturnData
Set rng = Nothing
Currently I'm trying to figure out how to add one more FOR which where I could gather more than one selection ranges for example:
Set myAnotherArray(0) = Range("H2", Range("H2").End(xlDown)).Select
Set myAnotherArray(1) = Range("J2", Range("J2").End(xlDown)).Select
For k = 1 To myAnotherArray.lenght
Because I'm copying and pasting whole script to make aciton on three columns. Tried already:
Dim Rng As Range
Dim Area As Range
Set Rng = Range("Range("H2", Range("H2").End(xlDown)).Select,Range("J2", Range("J2").End(xlDown)).Select")
For Each Area In Rng.Areas
Area.Font.Bold = True
Next Area
Even tried to Union range but I failed. Any sugesstions?
And as always... Thank you for your time!

I found a way you could use to perform work on those ranges, refer to the code below:
Sub DoSomethingWithRanges()
Dim m_Worksheet As Excel.Worksheet
Dim m_Columns() As Variant
Set m_Worksheet = ActiveSheet
' fill all your columns in here
m_Columns = Array(2, 3, 4)
For Each m_Column In m_Columns
' the area being used ranges from the second until the last row of your column
With m_Worksheet.Range(m_Worksheet.Cells(2, m_Column), m_Worksheet.Cells(m_Worksheet.UsedRange.Rows.Count, m_Column))
' do things with range
.Font.Bold = True
End With
Next m_Column
End Sub
In the variant array m_Columns you can add all the columns you want. Only downside is that in my example you have to use numbers to specify columns instead of "H". However, you don't have to worry about the row-indexes, since the area automatically ranges from the second to the last used row.

Related

VBA Modify Range when value is observed

I'm trying to get a sub to work that will color fields based on when the values "TRUE" or "FALSE" appears. I've already asked the below question, and have arrived at the code, also below.
VBA Excel Format Range when value is found
Option Explicit
Public Sub MarkCellsAbove()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet2")
Dim v As Variant
Dim i As Long, j As Long, n As Long, m As Long, r As Long, y As Long
Dim rng As Range
Dim rCell As Range
Dim DynamicArea As Range
Dim t As Double
' get last row in column C
n = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
' get last column from A
y = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
' set dynamic area to above values
Set DynamicArea = ws.Range(Cells(1, 1), Cells(n, y))
' clear existing colors over the WHOLE column to minimize file size
DynamicArea.Interior.ColorIndex = xlColorIndexNone
For Each rCell In DynamicArea
Select Case rCell.Text
Case "TRUE"
Set rng = rCell.Offset(-2, 0)
rng.Interior.ColorIndex = 4
Case "FALSE"
Set rng = rCell.Offset(-2, 0)
rng.Interior.ColorIndex = 5
End Select
Next
End Sub
This works well - I am able to color the cell 2 rows above where FALSE or TRUE is found. However - I would like to color not just this cell, but all cells in the range specified by Offset. So, if I specify 8 cells above, I would like to color 8 cells.
I hope someone can help - I'm so close to finishing this!
Try
Set rng = Range(rCell.Offset(-8, 0), rCell.Offset(-1, 0))
Note that you will get a runtime error if rCell is not at least in row 9

Trying to copy and paste filtered data over using best practices

I'm writing a very simple bit of code to move data from one workbook to another. I'm trying to avoid using select and copy-paste, since it's widely considered to not be optimal. Ok, challenge accepted. I've gotten just about everything written, and I've suddenly realized - I don't know how to define a range of filtered data as a range, ignoring the parts that are filtered out. I've done some searching, but I'm not quite there. Current code as follows:
Sub CSReport()
Dim CabReport As Workbook
Dim ExCashArchive As Workbook
Dim CABReconFilePath As String
Dim ExCashPath As String
Dim HoldingsTabName As String
Dim IMSHoldingsTabName As String
Dim HoldingsTab As Worksheet
Dim IMSHoldingsTab As Worksheet
Dim LastRowHoldings As Integer
Dim LastRowIMSHoldings As Integer
Dim RngHoldings As Range
Dim RngIMS As Range
Dim dt As Date
dt = Range("Today")
'Today is a named range with the date, just incase I need to be manually changing it
CABReconFilePath = Range("CABReconFilePath")
ExCashPath = Range("ExcessCashArchiveFilePath")
'What are the files we care about
HoldingsTabName = Range("HoldingTieOutTabName")
IMSHoldingsTabName = Range("IMSHoldingsTabName")
'What are the tab names we care about
Workbooks.Open Filename:=CABReconFilePath
Set CabReport = ActiveWorkbook
Workbooks.Open Filename:=ExCashPath
Set ExCashArchive = ActiveWorkbook
'Opening and defining the workbooks we're dealing with
HoldingsTab = ExCashArchive.Sheets(HoldingsTabName)
IMSHoldingsTab = ExCashArchive.Sheets(IMSHoldingsTabName)
'Defining the tabs
LastRowHoldings = HoldingsTab.Range("A" & Rows.Count).End(xlUp).Row
LastRowIMSHoldings = IMSHoldingsTab.Range("A" & Rows.Count).End(xlUp).Row
'Defining the edges of the data
'Filter goes here
RngHoldings = HoldingsTab.Range("A3:K" & LastRowHoldings)
RngIMS = IMSHoldingsTab.Range("A3:P" & LastRowIMSHoldings)
'Or maybe it goes here?
CABReconFilePath.Sheets("Holdings_TieOut").Range("A3").Resize(CopyFrom.Rows.Count).Value = RngHoldings.Value
CABReconFilePath.Sheets("IMS_Holdings").Range("A3").Resize(CopyFrom.Rows.Count).Value = RngIMS.Value
'Getting the values in
CABReconFilePath.Sheets("Recon Summary").Range("B1").Value = Text(dt, "MM/DD/YYYY")
'And setting the date manually, just incase we're running prior/future reports
ExCashArchive.Close savechanges:=False
CabReport.SaveAs Filename = CABReconFilePath & Text(dt, "MM.DD.YY")
CabReport.Close
End Sub
Now, what I've previously done is fairly clumsy things like:
Range("A1").Select
Range(Selection, Selection.End(xlToRight)).Select
Range(Selection, Selection.End(xlDown)).Select
Selection.AutoFilter
ActiveSheet.Range("$A$1:$W$71").AutoFilter Field:=1, Criteria1:="=*1470*", Operator:=xlFilterValues
Selection.Copy
CABReconFilePath.Sheets("CS").Range("C1").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.CutCopyMode = False
This has been my method until now of "Filter data, copy it, paste it somewhere else" - but I'm trying to learn better programming methods, and I keep hearing about "Don't use select" and "Try to avoid copy-pasting - move stuff into a range and use that instead!". But I'm stuck at this point.
Edit: .SpecialCells(xlCellTypeVisible) is the qualifier I needed to add.
Sub CopyFilterRange()
Dim i As Long
Dim j As Long
Dim lRow As Long
Dim cnt As Long
Dim UB1 As Long
Dim UB2 As Long
Dim rng1 As Range
Dim rng2 As Range
Dim arr1() As Variant
Dim arr2() As Variant
Dim WS1 As Worksheet
Dim WS2 As Worksheet
Set WS1 = ThisWorkbook.Sheets("Sheet1")
Set WS2 = ThisWorkbook.Sheets("Sheet2") 'this can be a different sheet in a different workbook
'Find last row in column A
With WS1
lRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
'Define range
Set rng1 = WS1.Range("A1:A" & lRow)
'Define array out of range
arr1 = rng1
'Redim array 2 rows based on the columns of array 1
'We will define it with one column and rows equal to the same number of columns in array 1
'The reason is that in arrays only the last index can be flexible and the other indices should stay fixed
UB1 = UBound(arr1, 1)
UB2 = UBound(arr1, 2)
ReDim arr2(1 To UB2, 1 To 1)
'Loop throug arr1 and filter
cnt = 0
For i = 1 To UB1
For j = 1 To UB2
If arr1(i, j) = "A" Or arr1(i, j) = "B" Then
cnt = cnt + 1
ReDim Preserve arr2(1 To UB2, 1 To cnt) 'here we can add one column to array while preserving the data
bResizeArray = False 'resizing array should happen only once in the inner loop
arr2(j, cnt) = arr1(i, j)
End If
Next j
Next i
'Transpose arr2
arr2 = TransposeArray(arr2)
'Paste arr2 value in the destination range
'Define the size of destination range
Set rng2 = WS2.Range("A1")
Set rng2 = rng2.Resize(UBound(arr2, 1), UBound(arr2, 2))
rng2.Value = arr2
End Sub
Public Function TransposeArray(myarray As Variant) As Variant
Dim X As Long
Dim Y As Long
Dim Xupper As Long
Dim Yupper As Long
Dim tempArray As Variant
Xupper = UBound(myarray, 2)
Yupper = UBound(myarray, 1)
ReDim tempArray(1 To Xupper, 1 To Yupper)
For X = 1 To Xupper
For Y = 1 To Yupper
tempArray(X, Y) = myarray(Y, X)
Next Y
Next X
TransposeArray = tempArray
End Function

Variable for RowNum of first and last integer in a column and creating a range between the 2

I want to create two variables, one for the row number of the first integer in column J and one for the last integer in column J (after this there are many rows of #N A N A). I then would like to use these two points to create a range for column J but also for column D (using the same variables)
I just started using vba earlier this week, understand the simplicity and I have found similar answers elsewhere but none quite so specific. Any help would be much appreciated. What I have so far:
Dim StartRow As Integer
Dim sht As Worksheet
Dim LastRow As Integer
Dim JRange As Range
Dim DRange As Range
Set StartRow = Range("j7:j100").SpecialCells(xlCellTypeConstants, xlNumbers).Cells(1, 1).Row
LastNonRow = sht.Cells(sht.Rows.Count, 8).End(xlUp).Row (I fear this includes the #N A N A rows too)
Set JRange = sht.Range(StartRow,10,sht.Cells(LastRow,10))
Set DRange = sht.Range(StartRow,4,sht.Cells(LastRow,4))
First, you should use:
Set JRange = sht.Range(sht.Cells(StartRow,10),sht.Cells(LastRow,10))
Second, don't use the Set keyword with Integer or other value types. The Set keyword is only for use with objects. If you are not familiar with object vs. value types, you should do some research on that. Here are some Excel VBA examples:
Dim i As Integer
Dim j As Double
dim str as String
Dim coll As Collection
dim sht As Worksheet
dim rng As Range
i = 1
j = 2.4
str = "This is a string of text, also a value type"
Set coll = ThisWorkbook.Sheets 'Note that ThisWorkbook.Sheets is a Collection
Set sht = coll(1) 'sets sht to the first Sheet in coll
Set rng = sht.Range("A1")
If you just care whether a number is in the cell and not necessarily an integer, you can do something like this
'Use Double instead of Integer because the possible range of values is larger.
'This reduces risk of error
Dim LastRow As Double
Dim UpperLimit As Double
'Assuming you already have StartRow
LastRow = StartRow + 1
'Use UpperLimit to ensure you don't go into an infinite loop
'Set UpperLimit to an appropriately high value
UpperLimit = 100000
'Test Loop
Do While IsNumeric(sht.Cells(LastRow,10).Value) and LastRow <= UpperLimit
LastRow = LastRow + 1
Loop
'Check if it exceeded upper limit
If LastRow > UpperLimit Then
MsgBox "Exceeded Limit"
Exit Sub
End If
LastRow = LastRow - 1 'Because the sht.Cells(LastRow,10).value is non-numeric
'Now set ranges
If you care whether it is actually an integer, you can use the above code, but put a conditional statement inside the loop to test whether the number is indeed an integer. There are many resources on the web that give code for determining whether value is an integer.
Also, you should make sure you set the sht variable before you use it.
Set sht = ThisWorkbook.Sheets(1)
Or
Set sht = ThisWorkbook.Sheets("MySheetName")
Hope this helps.
EDIT:
If the range is not continuous with integers, reverse the loop:
LastRow = UpperLimit
Do Until IsNumeric(sht.Cells(lastRow,10).Value)) Or LastRow = StartRow
LastRow = LastRow - 1
Loop
If you do this, remove the LastRow = LastRow-1 from the end of the code as the value you are looking for is already in LastRow.
There's a bunch of issues with your code:
Set StartRow - You only use Set when you're creating an object.
Also there's an issue with referencing the cells. There are a bunch of different ways to do this, I suggest to read up on this.
The following will take the first row with a number and the last row with a number, even if there are Non-number values in between. It will run on the currently active sheet.
Sub test()
Dim StartRow As Integer
Dim LastRow As Integer
Dim JRange As Range
Dim DRange As Range
Dim RangeForLoop As Range
StartRow = Range("J7:J100").SpecialCells(xlCellTypeConstants, xlNumbers).Cells(1, 1).Row
For Each RangeForLoop In Range("J7:J100").SpecialCells(xlCellTypeConstants, xlNumbers).Cells
LastRow = RangeForLoop.Row
Next RangeForLoop
Set JRange = Range("J" & StartRow & ":J" & LastRow)
Set DRange = Range("D" & StartRow & ":D" & LastRow)
End Sub

Checking if there is any data entered into a range of cells using VBA in Excel

I'm trying to call a Sub (New_Row) when the first empty row (minus the last column) is filled. I'm having trouble with how to reference a range of cells in the If statement toward the end.
Sub Data_Added()
'Check if anything has been entered into the first empty row in "Data"
Dim sht As Worksheet
Dim LastRow As Long
Dim LastColumn As Long
Dim StartCell As Range
Sheets("Data").Select
Set sht = Worksheets("Data")
Set StartCell = Range("A1").End(xlDown).Select
Worksheets("Data").UsedRange
LastRow = StartCell.SpecialCells(xlCellTypeLastCell).Row
LastColumn = StartCell.SpecialCells(xlCellTypeLastCell).Column
Set InputRange = sht.Range(StartCell, sht.Cells(LastRow + 1, LastColumn - 1))
If InputRange Is Not Nothing Then
Call New_Row
End If
End Sub
I've seen people using the Application.Intersect method, but I'm not sure if an intersect makes sense for just one row of cells. Totally new to VBA, though, so I don't know. Right now I'm getting an "Invalid use of Object" error pointing at the "Nothing" in the If statement.
Dim y As Long, lastx As Long
Dim sht As Worksheet
y = 1 'Row you want to check
Set sht = ThisWorkbook.Worksheets("Sheet1")
lastx = sht.Cells(y, sht.Columns.Count).End(xlToRight).Column - 1
If WorksheetFunction.CountA(Range(Cells(y, 1), Cells(y, lastx))) <> 0 Then 'Call New_Row when the row you are checking is empty
Call New_Row
End If
Have you tried something like this?

Similar Grouping in vba

I have two for loops in vba that are iterating over column b and checking to see if the first word in the current cell is the same as the first word in any other cell and if so copying them into another column, therefore grouping similar items. But, when I go to copy and paste the matches it finds, it only copy and pastes the matches, not the original cells that it is comparing against. I would like to have the matches and the original cells as well in the grouping but I am unsure where to modify my code so it will do so. I am rather new to vba so any help would be greatly appreciated.
Sub FuzzySearch()
Dim WrdArray1() As String, WrdArray2() As String, i As Long, Count As Long, Rng1 As Range
Dim WS As Worksheet, positionx As Long, positiony As Long
Dim rng2 As Range
Set WS = ThisWorkbook.ActiveSheet
With WS
Set Rng1 = .Range("B2:B" & .Range("B" & .Rows.Count).End(xlUp).Row)
End With
For i = 1 To Rng1.Rows.Count
With Columns("B")
.Find(what:="*", after:=.Cells(1, 1), LookIn:=xlFormulas).Activate
End With
position = 1
For j = 1 To Rng1.Rows.Count
WrdArray1 = Split(ActiveCell.Value, " ")
ActiveCell.Offset(1).Activate
WrdArray2 = Split(ActiveCell.Value, " ")
If UBound(WrdArray2) < 0 Then
End
End If
If WrdArray1(0) = WrdArray2(0) Then
ActiveCell.Copy Destination:=ActiveSheet.Range("C" & position)
position = position + 1
Count = Count + 1
End If
Next j
Next i
End Sub
Given that you are using a mixture of arrays and Ranges it would probably be easier and less confusing to populate one of the arrays with the final output (including the comparator) within a loop and then transfer the array to the worksheet in a single command.
However, perhaps consider the following approach which lets Excel do all the 'heavy lifting'. It's the same number of code lines but I have annotated it for your information. This illustrates the filling of an array in a loop and transferring it to a Range. Change the various variables to suit your situation.
Sub grpAndCount()
Dim ws As Worksheet
Dim strow As Long, endrow As Long, stcol As Long
Dim coloffset As Long, r As Long
Dim newstr As String
Dim drng As Range
Dim strArr() As String
'Data start r/c
strow = 6 'Row 6
stcol = 2 'Col B
'Offset no of Cols from Data to place results
coloffset = 2
Set ws = Sheets("Sheet1")
With ws
'find last data row
endrow = Cells(Rows.Count, stcol).End(xlUp).Row
'for each data row
For r = strow To endrow
'get first word
newstr = Left(.Cells(r, stcol), InStr(.Cells(r, stcol), " ")-1)
'put string into array
ReDim Preserve strArr(r - strow)
strArr(r - strow) = newstr
Next r
'put array to worksheet
Set drng = .Range(.Cells(strow, stcol + coloffset), .Cells(endrow, stcol + coloffset))
drng = Application.Transpose(strArr)
'sort newly copied range
drng.Sort Key1:=.Cells(strow, stcol + coloffset), Order1:=xlAscending, Header:=xlNo
'provide a header row for SubTotal
.Cells(strow - 1, stcol + coloffset) = "Header"
'resize range to include header
drng.Offset(-1, 0).Resize(drng.Rows.Count + 1, 1).Select
'apply Excel SubTotal function
Application.DisplayAlerts = False
Selection.Subtotal GroupBy:=1, Function:=xlCount, Totallist:=Array(1)
Application.DisplayAlerts = True
'remove 'Header' legend
.Cells(strow - 1, stcol + coloffset) = ""
End With
End Sub