I'm trying to combine the values in a range of cells. I created a range as follows:
Dim rng As Range
Set rng = Application.Union(Range("A1:A3"), Range("C1:E2"))
For Each Address In rng
Debug.Print Address.Address
Next
I want to read the cells in horizontal order: A1,C1,D1,E1,A2,C2... etc.
But instead they're being read A1,A2,A3,C1,C2,.. etc
How can I read them horizontally?
Thanks
You have to deal with the Range.Areas within the Range object created with the Union method.
Sub laterally()
Dim r As Long, c As Long, a As Long
Dim mnRW As Long, mxRW As Long, mnCL As Long, mxCL As Long
Dim rng As Range
With Worksheets("Sheet1") '<~~ ALWAYS set the worksheet!
Set rng = Union(.Range("A1:A3"), .Range("C1:E2"))
Debug.Print rng.Address(0, 0)
mnRW = Rows.Count: mxRW = 0
mnCL = Columns.Count: mxCL = 0
With rng
For a = 1 To .Areas.Count
With .Areas(a)
mnRW = Application.Min(mnRW, .Rows(1).Row)
mxRW = Application.Max(mxRW, .Rows(.Rows.Count).Row)
mnCL = Application.Min(mnCL, .Columns(1).Column)
mxCL = Application.Max(mxCL, .Columns(.Columns.Count).Column)
End With
Next a
For r = mnRW To mxRW
For c = mnCL To mxCL
If Not Intersect(.Cells, .Parent.Cells(r, c)) Is Nothing Then _
Debug.Print .Parent.Cells(r, c).Address(0, 0)
Next c
Next r
End With
End With
End Sub
After collecting the extents of the unioned range, each possible cell is looped through and the Intersect method is used to determine whether it belongs in the union or not.
A union of ranges doesn't care in which order the cells are added - whether it's by row or by column. So your original loop to unify the ranges is fine.
If you're concerned about the order in which you read the data, simply read by row or by column accordingly, with an inner and an outer loop. So for example, after you build the range, do as follows:
Dim Col as Variant, Rw as Variant
For Each Col in rng.Columns
For each Rw in Col.Rows
Debug.Print Rw.Address
Next Rw
Next Col
Just thought this might be an interesting addition to Chris' solution?
(I changed the range slightly to highlight some of the advantages)
Dim Rng As Range, Col As Variant, Rw As Variant
Set Rng = Application.Union(Range("A3:A5"), Range("C2:E4"))
For Each Rw In ActiveSheet.UsedRange.Rows
For Each Col In Rw.Columns
If Not Intersect(Col, Rng) Is Nothing Then Debug.Print Intersect(Col, Rng).Address
Next Col
Next Rw
Related
I have 10 records in excel of which i have edited 3rd and 7th records and placing a flag/string "modified" in certain column belongs to same rows to filter while processing
Below is the code that i am working with which is fetching only the first record(3rd) and not the 7th record into array using VBA
Dim RecordsArray() As Variant
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.count
lw = [a2].End(xlDown).Row
RecordsArray = Range(Cells(2, 1), Cells(lw,col)).SpecialCells(xlCellTypeVisible)
Idea is I want to get those two records without looping and searching for
"Modified" string for the edited row
When reading a Filtered Range, most likely there will be splits ranges, the rows will not be continuous, so you need to loop through the Areas of the Filtered Range.
Also, you might have a few Rows in each Area, so you should loop through the Area.Rows.
More detailed comments in my code below.
Code
Option Explicit
Sub Populated2DArrayfromFilterRange()
Dim RecordsArray() As Variant
Dim sht As Worksheet
Dim col As Long, lw As Long, i As Long
Dim FiltRng As Range, myArea As Range, myRow As Range
ReDim RecordsArray(0 To 1000) ' redim size of array to high number >> will optimize later
' set the worksheet object
Set sht = ThisWorkbook.Sheets("RMData")
i = 0 ' reset array element index
' use With statement to fully qualify all Range and Cells objects nested inside
With sht
.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = .Range("A2").CurrentRegion.Columns.Count
lw = .Range("A2").End(xlDown).Row
' set the filtered range
Set FiltRng = .Range(.Cells(2, 1), .Cells(lw, col)).SpecialCells(xlCellTypeVisible)
' Debug.Print FiltRng.Address(0, 0)
For Each myArea In FiltRng.Areas ' <-- loop through areas
For Each myRow In myArea.Rows ' <-- loop through rows in area
RecordsArray(i) = Application.Transpose(Application.Transpose(myRow))
i = i + 1 ' raise array index by 1
Next myRow
Next myArea
ReDim Preserve RecordsArray(0 To i - 1) ' optimize array size to actual populated size
End With
End Sub
If you have a hidden row in the middle, then .SpecialCells(xlCellTypeVisible) will return multiple Areas. Assigning a range to an Array only assigns the first Area. (At also always makes the array 2D)
Instead of looping & searching for "Modified", you could just loop For Each cell in the SpecialCells range and assign that to the array instead - if you plan was "no loops at all" then this is not what you want. (But, I would then have to ask you "why not?"!)
Dim RecordsArray() As Variant, rFiltered As Range, rCell As Range, lCount As Long
Set sht = ThisWorkbook.Sheets("RMData")
sht.Range("M1:M100").AutoFilter Field:=1, Criteria1:="Modified"
sht.Range("A2:A100").Rows.SpecialCells (xlCellTypeVisible)
col = [a2].CurrentRegion.Columns.Count 'This will act on ActiveSheet, not sht - is that intended?
lw = [a2].End(xlDown).Row 'In case of gaps, would "lw=sht.Cells(sht.Rows.Count,1).End(xlUp).Row" be better?
'RecordsArray = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
Set rFiltered = Range(Cells(2, 1), Cells(lw, col)).SpecialCells(xlCellTypeVisible)
ReDim RecordsArray(1 To rFiltered.Cells.Count, 1) 'Mimic default assignment
lCount = 1
For Each rCell In rFiltered
RecordsArray(lCount, 1) = rCell.Value
lCount = lCount + 1
Next rTMP
Set rCell = Nothing
Set rFiltered = Nothing
If you want to avoid dealing with the visible areas mentioned already, you can try something like this
Option Explicit
Public Sub CopyVisibleToArray()
Dim recordsArray As Variant, ws As Worksheet, nextAvailable As Range
Set ws = ThisWorkbook.Worksheets("RMData")
Set nextAvailable = ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(2)
With ws.Range("M1:M100")
Application.ScreenUpdating = False
.AutoFilter Field:=1, Criteria1:="Modified"
If .Rows.SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
'copy - paste visibles in col A, under all data
ws.UsedRange.Columns("A:M").SpecialCells(xlCellTypeVisible).Copy nextAvailable
Set nextAvailable = nextAvailable.Offset(1)
nextAvailable.Offset(-1).EntireRow.Delete 'Delete the (visible) header
recordsArray = nextAvailable.CurrentRegion 'Get the cells as array
nextAvailable.CurrentRegion.EntireRow.Delete 'Delete the temporary range
End If
.AutoFilter
Application.ScreenUpdating = True
End With
End Sub
To copy just column A to array use this: ws.UsedRange.Columns("A")
To copy columns A to M use this: ws.UsedRange.Columns("A:M")
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
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.
I need to set a range to select only the cells in a row which contain data.
Sometimes there will be data in columns B, C, D, E and F of row 3, whilst at other times there will be data in the first 10 or 20 columns of that row.
I've tried the below, but it doesn't work.
Dim rRng As Range
Set rRng = Sheets(1).Range("B3").End(xltoright)
I know that I'm wide of the mark, but am struggling with this.
This code will be run for a variety of different datasets; sometimes it needs to select five cells, sometimes ten (if populated).
Select to the last column or select non blanks
Sub SelectLstCol()
Dim Col As Long
Dim rng As Range
Col = Cells(3, Columns.Count).End(xlToLeft).Column
Set rng = Range(Cells(3, 2), Cells(3, Col))
rng.Select 'or whatever you want to do with it
End Sub
Sub NonBlanks()
Dim Col As Long
Dim rng As Range
Col = Cells(3, Columns.Count).End(xlToLeft).Column
Set rng = Range(Cells(3, 2), Cells(3, Col)).SpecialCells(xlCellTypeConstants, 23)
rng.Select 'or whatever you want to do with it
End Sub
Using a function to be able to reuse it more easily :
Sub test_andrew_abbott()
MsgBox Get_Row_Range(5).Address
MsgBox Get_Row_Range(Range("A12").Row).Address
End Sub
This will be more robust than with .End(xlToRight) :
Public Function Get_Row_Range(RowNumber As Long) As Range
Dim wS As Worksheet, _
rRng As Range
Set wS = Sheets(1)
With wS
Set rRng = .Range(.Range("A" & RowNumber), _
.Cells(RowNumber, .Columns.Count).End(xlToLeft))
End With
Set Get_Row_Range = rRng
End Function
You can connect to your excel file as a data set, I find it better than other way
I have the first cell in the row saved as a range and I just wanted to know how I would go about bringing in the whole row so I would be able to compare the two. Any help would be greatly appreciated.
Sub crossUpdate()
Dim rng1 As Range, rng2 As Range, N As Long, C As Long
N = Cells(Rows.Count, "A").End(xlUp).row
Set rng1 = Sheet1.Cells.Range("A2:A" & N)
C1 = rng1.Rows.Count
Set rng1 = Sheet2.Cells.Range("A2:A" & N)
C2 = rng2.Rows.Count
For i = 2 To C
End Sub
I notice a typo in your code, as indicated in the comments you're double-assigning to the rng1 variable, change the second to Set rng2 = ...
You have a loop For i = 2 to C but you have never assigned anything to the variable C, so that will not cause an error, but it will fail to do what you hope it will do.
Option Explicit 'use this to force variable declaration, avoids some errors/typos
Sub crossUpdate()
'Declare each variable separately, it is easier to read this way and won't raise problems
' in some cases if you need to pass to other subs/functions
Dim rng1 As Range
Dim rng2 As Range
Dim N As Long
'Add these declarations
Dim C As Long
Dim R as Long
'I deleted declarations for i (replaced with R), C, N which won't be needed. And also C1, C2
'I'm going to declare some additional range variables, these will be easier to work with
Dim rng1Row as Range
Dim rng2Row as Range
Dim cl as Range
N = Cells(Rows.Count, "A").End(xlUp).row
Set rng1 = Sheet1.Cells.Range("A2:A" & N)
Set rng2 = Sheet2.Cells.Range("A2:A" & N) 'This line was incorrect before
'Now to compare the cells in each row
For R = 2 to rng1.Rows.Count
Set rng1Row = rng1.Cells(R,1).EntireRow
Set rng2Row = rng2.Cells(R,1).EntireRow
For C = 1 to rng1.Columns.Count
If rng1Row.Cells(R,C).Value <> rng2Row.Cells(R,C).Value Then
'Do something if they are NOT equal
Else
'Do something if they ARE equal
End If
Next
End Sub
There are actually some simpler ways to do this, probably, but for purpose of demonstration it is easier for me to explain by breaking it down like this. But for example, range's aren't limited by the number of cells they contain. Consider this:
Debug.Print Range("A1").Cells(,2).Address
Should this raise an error? After all, [A1] is a single cell. It won't raise an error, and instead it will correctly print: $B$1.
So you could probably simplify to this, and avoid using the rng1Row and rng2Row variables:
For R = 2 to rng1.Rows.Count
For C = 1 to rng1.Columns.Count
If rng1.Cells(R,C).Value <> rng2.Cells(R,C).Value Then
'Do something if they are NOT equal
Else
'Do something if they ARE equal
End If
Next
End Sub