Traversing `Cells` in a `Range` - vba

Short version of the question:
The code here
Dim rng As Range
Set rng = Selection
Set rng = rng.Columns(1)
For Each cl In rng
cl.Select ' <-- Break #2
gives me this in the immediate window when the selection is A1:B37
? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37
? cl.Address(External:=True)
[Book2]Sheet1!$A$1:$A$37
Anyone can help me understanding why cl -> A1:A37 instead of cl -> A1?
Note that I imagine rewriting code to get the intended results. But I would like to know what is the problem, and probably learn something new along. This is what the question is about.
Long version of the question (as originally posted):
I have a subroutine, which works on the selected (rectangular) range rng. The code of relevance here is shown below. It branches depending on the number ncols of columns of rng.
When ncols=1, it loops through each cell cl in rng, selecting cl and performing some actions.
When the starting selection is A1:A37, this works ok, as shown by the output in the immediate window right after entering the loop at Break #1 (see code below)
? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37
? cl.Address(External:=True)
[Book2]Sheet1!$A$1
When ncols<>1, I want to loop through each cell cl in the first column of rng, doing the same as before.
Now when the starting selection is A1:B37, this does not work, as shown by the output in the immediate window at Break #2
? rng.address(External:=True)
[Book2]Sheet1!$A$1:$A$37
? cl.Address(External:=True)
[Book2]Sheet1!$A$1:$A$37
Anyone can help me understanding why here cl -> A1:A37 instead of cl -> A1 (as in Break #1)?
Note that I imagine rewriting code to get the intended results. But I would like to know what is the problem, and probably learn something new along. This is what the question is about.
Dim rng As Range
Set rng = Selection
Dim ncols As Long
ncols = rng.Columns.Count
Dim cl As Range
' 1- If only one column is selected, ...
If (ncols = 1) Then
For Each cl In rng
cl.Select ' <-- Break #1
...
Next cl
' 2- If more than one column is selected, ...
Else
Set rng = rng.Columns(1)
For Each cl In rng
cl.Select ' <-- Break #2
Dim rng2 As Range
Set rng2 = Range(cl, cl.Offset(0, ncols - 1))
rng2.Select
...
Next cl
End If

I have not had a chance to test your code yet but you may simply be suffering from lack of explicity: cl is a Range, so is a Column and a Row and an Area and any other sort of range-type object. You can use a range iterator like cl : For each cl in Rng.Rows or ...in rng.Columns, or in ...rng.Cells, etc.
In other words, while you may be expecting cl to be a cell range, that may not be the case unless you make it explicit, like:
For each cl in rng.Cells
Or, since you are defining it as a single-column, this would be equivalent:
For Each cl in rng.Rows
(technically, cl represents a row range in that rng, but since it's a single column range, each "row" is a single cell, too).
Your code can acutally be quite streamlined:
Sub f()
Dim rng As Range
Dim cl As Range
Dim rng2 As Range
Set rng = Range(Selection.Address).Resize(, 1)
ncols = Range(Selection.Address).Columns.Count
For Each cl In rng.Cells
cl.Select ' <-- Break #2
If nCols > 1 Then
Set rng2 = Range(cl, cl.Offset(0, ncols - 1))
rng2.Select
'...
End If
Next cl
End Sub

Related

Move a Range with offset

I'm having a trouble by moving a designated row with an offset up.
It throws me the Error 424 that needs an Object.
I set up the rng2 with the selected range, but when i try to move it up, the error get in.
I basically need that when the area in the 87 row finds an empty cell delete it so the label with the info can go up.
Just
to
Thanks for your time!
Sub RowOffset()
Application.ScreenUpdating = False
'Worksheets("Mine").Activate
Dim rng As Range
Dim rng2 As Range
Dim i As Long
Set rng = ThisWorkbook.ActiveSheet.Range("C87:C37")
Set rng2 = Range("C85:N85")
With rng
For i = .Rows.Count To 1 Step -1
If .Item(i) = "" Then
rng2.Select
rng2.Delete Shift:=xlUp
Set rng2 = rng2.Offset(-1, 0) 'THIS LINE HAS AN ERROR
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
You can't .offset() a range that has been deleted before. Try this instead (not tested, I am answering on a smartphone):
With rng
For i = .Rows.Count To 1 Step -1
If .Item(i) = "" Then
set tmp=rng2
tmp.Delete Shift:=xlUp
End If
Set rng2 = rng2.Offset(-1)
Next i
End With
The rng2.offset(-1) (the second argument, 0, is optional) should not be inside the if-clause.
There is also no need to .select a range before deleting it. In fact, leaving this step out could improve the performance of your script.
Edit:
Well, as #Matto quite rightly remarked, my former solution still did not work. So I had another look at it and modified the whole sub as follows:
Sub rowOffset()
Dim rng As Range, row As Range, i%
Set rng = [c37:n85]
For i = rng.Rows.Count To 1 Step -1
Set row = rng.Rows.Item(i)
If row.Range("a1") = "" Then row.Delete shift:=xlUp
Next i
End Sub
I think this is shorter anyway ... and it works (tested).
The reason your code errors is because after rng2.Delete ... rng2 no longer points to a valid range (because you just deleted it).
That said a better approach would be to first identify the range to delete, then delete it in one go.
Something like this (assumes your first data is in column B)
Sub Demo()
Dim r1 As Range, r2 As Range
With ThisWorkbook.ActiveSheet
' Find Total row
' assumes there is no other data below the table in Column B
Set r2 = .Cells(.Rows.Count, 2).End(xlUp).Offset(-2, 0)
If IsEmpty(r2) Then
' Find the last row of data
Set r1 = r2.End(xlUp).Offset(1, 0)
.Range(r1, r2).EntireRow.Delete
End If
End With
End Sub
Range(“C35”).End(xlDown).Offset(1,0)
will give you the first cell in the first row to delete.
Then
Range(“C35”).End(xlToRight).End(xlDown).End(xlDown).Offset(-2,0)
Will give you the bottom right cell in the area that you want to delete.
So, set these two cells into variables (e.g. cell1 and cell2).
Then:
Range(cell1, cell2).Delete Shift:=xlUp
will land you at the result.

How to To Loop through cells in a column, and to find the latest date of the list in Excel VBA

Actual work to loop through the Column A which has same value of with certain range, with that range have to check the latest date with the comment in Column B and print the comment with date in Column C kindly help me to find the solution for this problem.. Or Guide to find the solution for this problem... Kindly check the Screenshot for clear information.. Thanks in Advance Experts
Loops are probably the most powerful things is all computer programming topics. Please see the examples below for some ideas of how to achieve your goals.
For Each cell in a range
data for each example
One of the most common things you will do when programming VBA in Excel is looping though a collection of cells in a specified range, as in the example below which prints the Address and Value of 4 cells on the 'Data' worksheet to the immediate window: $B$2:a, $C$2:b, $B$3:1, $C$3:2.
Dim rng As Range: Set rng = Application.Range("Data!B2:C3")
Dim cel As Range
For Each cel In rng.Cells
With cel
Debug.Print .Address & ":" & .Value
End With
Next cel
Loop through the cells in a row
data for each example
The code below shows how to loop through the cells in the row with RowIndex:=2. Applied to the data in the sheet on the right this will return 1, 2. From this we see that rows are counted from the starting point of rng, so the row is 3 on the worksheet, 2 inside rng. Also, only cells inside the set range rng are taken.
Dim rng As Range: Set rng = Application.Range("Data!B2:C3")
Dim i As Integer
For i = 1 To rng.Rows.Count
Debug.Print rng.Cells(RowIndex:=2, ColumnIndex:=i).Value
Next
Loop through the cells in a column
The code below shows how to loop through the cells in the column with ColumnIndex:=B. Applied to the data in the sheet on the right this will return a, 1, 2. From this we see that columns are counted from the starting point of rng, so the column is C on the worksheet, B inside rng. Also, only cells inside the set range rng are taken.
Dim rng As Range: Set rng =
Dim i As Integer
For i = 1 To rng.Rows.Count
Debug.Print rng.Cells(RowIndex:=i, ColumnIndex:="B").Value
Next
Loop through the columns in a range
The code below shows how to loop through the columns in the Range B2:C4. Applied to the data in the sheet on the right this will return 2, 3. From this we see that columns are counted from the starting point of the worksheet.
Dim rng As Range: Set rng = Application.Range("B2:C4")
Dim col As Range
For Each col In rng.Columns
Debug.Print col.Column
Next col
Loop through the rows in a range
The code below shows how to loop through the rows in the Range B2:C4. Applied to the data in the sheet on the right this will return 2, 3, 4. From this we see that rows are counted from the starting point of the worksheet.
Dim rng As Range: Set rng = Application.Range("B2:C4")
Dim col As Range
For Each col In rng.Rows
Debug.Print col.Row
Next col
Loop through the areas in a range
data for each 2 areas example
Often we assume a range to have a rectangular shape, but this need not be the case. The example sheet on the right shows a selection containing two areas: Selection.Address returns $B$2:$C$3,$F$2:$F$3. Such a situation may also occur as a result of the Intersect method, or other causes. To handle the two ranges separately can can pick then from the Areas collection:
Dim rng As Range: Set rng = Application.Selection
Dim rngArea As Range
For Each rngArea In rng.Areas
Debug.Print rngArea.Address
Next rngArea
I would recommend declaring some dimensions in a fairly simple approach (assumes you have sorted Column A):
Dim i As Long, j As Long, k As Long, LR As Long
LR = Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To LR
If Cells(i, 1).Value = Cells(i - 1).Value Then
If j = 0 Then
j = Cells(i - 1, 1).Row
End If
Else
If j > 0 Then
k = Cells(i - 1, 1).Row
Cells(j, 3).Value = Application.Max(Range(Cells(j, 1), Cells(k, 1)))
j = Cells(i, 1).Row
k = 0
End If
End If
Next i

excel vba set one range object by other two range object

Here is my problem. I have two Range Object. For example,
Set rg3 = Range("B2")
Set rg4 = Range("B3000")
I want to do this
Range("rg3:rg4").PasteSpecial (xlPasteAll)
But it show error. How can I select the region by two range object.
Range("B2:B3000") is not correct in my case because those two range would always updated by offset function.
Thanks for your help!!!
When you enter Range( the intellisense will show Range(Cell1, Cell2) as Range indicating the the Range object is expecting two cells.
So, seeing as rg3 and rg4 are two cells you can use Range(rg3, rg4).
You're using xlPasteAll so you could just use RangeBeingCopiedReference.Copy Destination:=Range(rg3,rg4)
Edit - and as #Robin says, what do you mean by the offsetting?
Edit 2:
If you want to loop through a range then using Cells is easier as it accepts a column number rather than a column letter.
This example will copy columns A:J over to U:AD one column at a time.
Sub Test()
Dim rg3 As Range, rg4 As Range
Dim x As Long
With ThisWorkbook.Worksheets("Sheet1")
For x = 1 To 10
.Range(.Cells(2, x), .Cells(3000, x)).Copy _
Destination:=.Range(.Cells(2, x + 20), .Cells(3000, x + 20))
Next x
End With
End Sub
Also - look up reference on WITH... END WITH - https://msdn.microsoft.com/en-us/library/wc500chb.aspx
I'd like to better understand your needs for better help
as a start, since your using of .PasteSpecial xlPasteAll I'd believe you're setting a source range outside a loop and paste it multiple times inside this latter shifting pasting range
you also explained "rg3 and rg4 is inside a for loop, each time it will move to next colmn by offset(0, 1)"
so this would initially lead to:
Option Explicit
Sub main()
Dim copyRng As Range, rg3 As Range, rg4 As Range
Dim i As Long
Set rg3 = Range("B2") '<~~ your rg3 range setting
Set rg4 = Range("B3000") '<~~ your rg4 range setting
Set copyRng = ... '<~~ your setting of the "source" range to be copied once and pasted many
copyRng.Copy '<~~ copy "source" Range once ...
With Range(rg3, rg4) '<~~ ... set your initial "target" range ...
For i = 1 To 10
.Offset(, i).PasteSpecial xlPasteAll '<~~ ... and paste "source" range offseting "target" once
Next i
End With
End Sub
but this would also be uselessly long and slow, since you could simply write:
Option Explicit
Sub main()
Dim copyRng As Range, rg3 As Range
Set rg3 = Range("B2") '<~~ just set the "beginning" of the target range
Set copyRng = ... '<~~ your setting of the "source" range to be copied once and pasted many
copyRng.Copy copyRng.Copy Destination:=rg3.Resize(, 10)
End Sub
so what's your real need?

Color coding cells based on value through a column with specific header

I have this code in vba that defines arrays for column headers that I want copy/pasted in another tab in Excel. However, in one of the new tabs, I also want to color code some cells based on their value in the column "BOM PROCESS TYPE (A, U, R, D)" which corresponds to position 2 in that array. The code runs without giving me an error, but the cells don't change color at all. Skipping some parts, this is what I have, does anyone know how to fix it?
'My variables.
Dim i As Long, rngCell As Range, rCell As Range
Dim c As Long, v As Long, vMHDRs As Variant, vBHDRs As Variant
Dim s As Long, vNWSs As Variant, wsMM As Worksheet
vBHDRs = Array("BOM LEVEL", "BOM PROCESS TYPE (A, U, R, D)", "ALTERNATIVE ITEM: GROUP")
'Skipping most of the code and jumping to the color coding section:
With Sheets("BOM")
v = 2
Set rngCell = Sheets("BOM").UsedRange.Find(What:=vBHDRs(v), LookAt:=xlWhole)
If Not rngCell Is Nothing Then
Set rngCell = Intersect(Sheets("BOM").UsedRange, rngCell.EntireColumn)
For Each rCell In rngCell
If rCell.Value = "D" Then rCell.Interior.ColorIndex = 3
If rCell.Value = "R" Then rCell.Interior.ColorIndex = 6
If rCell.Value = "U" Then rCell.Interior.ColorIndex = 6
Next
End If
End With
Any thoughts?
When you use With construction, you shouldn't use Sheets("BOM"), should you?
Set rngCell = .UsedRange.Find(What:=vBHDRs(v), LookAt:=xlWhole)
I have just simulated your colouring code and got it working. I believe your problem is the v=2 line
This is because of the way you have allocated your array and the default excel settings. The lower boundary for an array when allocated using your method above is 0, so this means you v=2 is refering to the "ALTERNATIVE ITEM: GROUP" column and so it is not finding D,R or U in that column.
You can either change to V=1 (and this works) or set option base 1 at the top of your module as this will change the default lower boundary to 1. I actually advise against the option base 1 if you have multiple modules as if you forget to put option base 1 at the top of all of them, you might get unexpected results.
As mentioned above, you dont need the Sheets("BOM") inside yoru With block, but it doesn't affect it working. This is the very slightly amended code that works for me
Sub test2()
Dim i As Long, rngCell As Range, rCell As Range
Dim c As Long, v As Long, vMHDRs As Variant, vBHDRs As Variant
Dim s As Long, vNWSs As Variant, wsMM As Worksheet
vBHDRs = Array("BOM LEVEL", "BOM PROCESS TYPE (A, U, R, D)", "ALTERNATIVE ITEM: GROUP")
With Sheets("BOM")
v = 1
Set rngCell = Sheets("BOM").UsedRange.Find(What:=vBHDRs(v), LookAt:=xlWhole)
If Not rngCell Is Nothing Then
Set rngCell = Intersect(Sheets("BOM").UsedRange, rngCell.EntireColumn)
For Each rCell In rngCell
If rCell.Value = "D" Then rCell.Interior.ColorIndex = 3
If rCell.Value = "R" Then rCell.Interior.ColorIndex = 6
If rCell.Value = "U" Then rCell.Interior.ColorIndex = 6
Next
End If
End With
End Sub
If your target sheets and the logic around the colouring of cells is consistent, then could you not achieve your desired objective using conditional formatting on the target sheets cells. Then all you macro needs to do is the copying.

Get entire row excel vba

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