Loop columns using VBA - excel

I want to loop through columns.
I'm not sure about the range. Should I use normal range where we have the alphabets as columns?
I tried different codes. For example:
Sub copytest()
Dim x As Workbook
Dim j As Integer, i As Integer
Application.ScreenUpdating = False
Workbooks.Open ("D:\test\COMP.xlsx")
i = 3
For j = 3 To 14
Sheets("Compliance").Range(Cells(18, i), Cells(30, i)).Copy
Windows("KP.xlsm").Activate
Sheets("MOH").Range(Cells(12, j), Cells(24, j)).PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
i = i + 2
Next j
x.Close
End Sub

You can use numbers for columns. Let's say you want to loop through first 5 rows and first 5 columns then you can write it as
For i = 1 To 5 '<~~~ Rows
For j = 1 To 5 '<~~~ Columns
With ThisWorkbook.Cells(i, j)
'~~> Do something
End With
Next j
Next i
In Excel 2013, the last column is XFD which is column number 16384.
A - 1
B - 2
C - 3
.
.
and so on...
XFD - 16384
If you would like to loop using Column Names then you can use it as such
Dim ColName As String
For i = 1 To 5 '<~~~ Rows
For j = 1 To 5 '<~~~ Columns
ColName = Split(Cells(, j).Address, "$")(1)
With ThisWorkbook.Range(ColName & i)
'~~> Do something
End With
Next j
Next i

an alternative for looping though columns is a For Each cell in my1RowRange loop, where
cell is a single-cell Range object that loops over my1RowRange Range
my1RowRange must be a 1-Row Range
besides that, your code has some major flaws:
your range references use simple Cells(...) reference without any explicit worksheet and workbook reference before them
so that they keep referencing the active worksheet in the active workbook
and this is no good since during your loop you only seem to keep changing the right workbook while you're actually only changing it once at the beginning (Windows("KP.xlsm").Activate) and then it always remains the only referenced workbook
avoid Activate/ActiveXXX (as well as Select/Selection) coding, since
it's error prone, like it proved to be right here, since it most likely lead you to loose control over actual active workbook/worksheet
it's time consuming and gets screen flickerings
so consider the following refactoring of your code:
Option Explicit
Sub copytest()
Dim i As Long
Dim compWb As Workbook
Dim compRng As Range, cell As Range
Set compWb = Workbooks.Open "D:\test\COMP.xlsx" '<--| set the workbook to open to a specific variable of 'Workbook' type
With compWb.Worksheets("Compliance") '<--| refer to "compliance" worksheet of the "right" (you're explicitly referencing it!) workbook
Set compRng = .Range(.Cells(18, 3), .Cells(30, 3)) '<--| set the "source" range: all the dots means we're referencing the object after the last "With" statement
End With
i = 3
With Workbooks("KP.xlsm").Worksheets("MOH") '<--| refer to "MOH" worksheet of "KP" workbook
For Each cell In .Range(.Cells(12, 3), .Cells(12, 14)) '<--| loop through cells of a 1-row range of the referenced worksheet: this means looping through columns!
cell.Resize(13).Value = compRng.Offset(, i - 3).Value '<--| paste values while offsetting the "souce" range columns (first iteration with offset=0)
i = i + 2
Next cell
End With
compWb.Close '<--| close the opened workbook
End Sub

Sub Columns()
Dim rng As Range
Dim col As Range
Sheets("Sheet1").Select 'make shure its in the correct sheet
Set rng = Range("C3:G6") 'select the range of the whole table
For Each col In rng.Columns 'this performs a tasks column by column
'Do Something
col.Select
Selection.RemoveDuplicates Columns:=1, Header:=xlNo
'end something
Next col 'move to next column
End Sub

Related

How to copy and paste range instead of row?

I am putting together a basic inventory control system and I would like the columns with a time-stamp in the "Checked-Out" column to be pasted into a list on another worksheet. I have successfully copied the correct entire rows, but I would like this to just copy and paste the table rows instead because I have instructions listed in column A that are not relevant for the compiled list. I am new to VBA coding, thanks in advance!
I have named ranges for the two tables called "Inventory_List": Inventory!$I$3:$N$1048576 and "Checked_Out": CheckedOut!$B$3:$G$1048576 as the copy/paste ranges respectively.
Sub testIt()
Dim r As Long, endRow As Long, pasteRowIndex As Long
endRow = 1000 ' of course it's best to retrieve the last used row number via a function
pasteRowIndex = 1
For r = 1 To endRow 'Loop through sheet1 and search for your criteria
If Cells(r, Columns("N").Column).Value > 0 Then 'Found
'Copy the current row
Rows(r).Select
Selection.Copy
'Switch to the sheet where you want to paste it & paste
Sheets("CheckedOut").Select
Rows(pasteRowIndex + 5).Select
ActiveSheet.Paste
'Next time you find a match, it will be pasted in a new row
pasteRowIndex = pasteRowIndex + 1
'Switch back to your table & continue to search for your criteria
Sheets("Inventory").Select
End If
Next r
End Sub
When I try to reference ranges instead of entire rows, I get "run-time error 1004" because my copy area and paste area aren't the same size, but I am a bit confused because my ranges seem to be the same size. I am pretty sure this is because I am adding the ranges to the incorrect portion of the code.
Copying and pasting of Excel ranges is quite standard, if you take into account 2 things:
Refer to the ranges correctly with the upper left cell and the lower right cell;
Always, refer to the Parent worksheet.
In the code below, the upper left cell and the lower right cells of the copied and pasted ranges are like this:
.Range(.Cells(count, 1), .Cells(count, "C"))
copyTo.Range(copyTo.Cells(count, 1), copyTo.Cells(count, "C"))
The parent worksheets are always referred. With with for the copyFrom and with explicit writing for copyTo.
Sub TestMe()
Dim copyFrom As Worksheet
Dim copyTo As Worksheet
Set copyFrom = Worksheets(1) 'Or better write the name - Worksheets("CheckedOut")
Set copyTo = Worksheets(2)
Dim count As Long
For count = 1 To 30
With copyFrom
If .Cells("N", count) > 0 Then
.Range(.Cells(count, 1), .Cells(count, "C")).Copy Destination:=copyTo.Range(copyTo.Cells(count, 1), copyTo.Cells(count, "C"))
End If
End With
Next
End Sub
Last, but not least - this is a must read for VBA - How to avoid using Select in Excel VBA

Excel VBA add value in specific cells

Got help from you guys to create a code to copy some value from one workbook to another. I have one more question though.
I would like to write som static information in a cell in column J on the same row as the other copied information.
How do I add that to the loop?
I´ve tried:
wsDest.Cells(DestRow, "J").Value = "HVD"
But it only adds the value in the first row not the rest. No errors but it seems as it doesn't loop as the other code inside the For Each.
Option Explicit
Public Sub CopyCells()
Dim wsSrc As Worksheet 'define source sheet
Set wsSrc = ThisWorkbook.Worksheets("Blad1")
Dim wbDest As Workbook 'define destination workbook
Set wbDest = Workbooks.Open("C:\Temp\Ändringar bef objekt.xlsx")
Dim wsDest As Worksheet 'define destination sheet
Set wsDest = wbDest.Worksheets("Ändringsdata")
Dim DestRow As Long
DestRow = 2 'start in row 2 in destination sheet
wsSrc.Parent.Activate: wsSrc.Activate
Dim Rng As Range
For Each Rng In Selection.Areas
Rng.Resize(, 1).Copy Destination:=wsDest.Cells(DestRow, "A") 'copy A to A
Rng.Resize(, 1).Offset(, 5).Copy Destination:=wsDest.Cells(DestRow, "D") 'copy F to D
**wsDest.Cells(DestRow, "J").Value = "HVD" 'write HVD in column J same row**
DestRow = DestRow + Rng.Rows.Count 'move DestRow to next free row
Next Rng
End Sub
I think you misunderstand how your For Each works. It doesn't loop through every row or every cell. It loops through Areas, which is a collection of Ranges. Usually there's only one Range in this collection, but if you select cells with Ctrl then you have more elements in Areas.
So, your code takes one such "Area" and copy it's first column to column A, then it's 5th column to column D and enters "HVD" into a single cell (Cells(DestRow, "J")). If you want to enter this value in every row you must resize this cell. And to do this you can use this:
wsDest.Cells(DestRow, "J").Resize(Rng.Rows.Count).Value = "HVD"
Also, remember that you can debug your code by putting cursor inside your macro code and pressing F8. This will run your code line by line and all changes will be immediately seen in the worksheet.

Update of sheet does not cause action until I run vba module twice

New to VBA
I'm confused as to why I need to run my module twice to get it to update my cells. My code:
Option Explicit
Sub m_Range_End_Method()
Dim lRow As Long
Dim lCol As Long
Dim currentRow As Long
Dim i As Integer
Dim rng As Range
Set rng = ActiveCell
Range("B:B").Select
lRow = Cells(Rows.Count, 1).End(xlUp).Row
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
Sheets("MySheet").Select
' Loop Through Cells to set description in each cell
Do While rng.Value <> Empty
currentRow = ActiveCell.Row
If InStr(rng.Value, "PETROL") = 0 Then
Set rng = rng.Offset(1)
rng.Select
Else
Worksheets("MySheet").Cells(currentRow, 5) = "Shopping"
Worksheets("MySheet").Cells(currentRow, 6) = "Car"
Set rng = rng.Offset(1)
rng.Select
End If
Loop
End Sub
On the first run what happens in Excel 2016 is that Column B gets highlighted and that's it. I then have to press "Run" again in visual basics editor for it to then update all the entries at which point column B gets unselected. All I want to do is update the cells at the currentRow of a specified worksheet. I've been reading but have got myself into some confusion, someone said I should use the
Range("B:B").Select
statement and for some reason the spreadsheet update works but only if I run it twice. Without this Range command, for reasons I don't understand, the spreadsheet doesn't update - all that happens is that the box selection moves to entries with Petrol and stays there with the program running but not updating.
The aim of the program is to find in a sheet all occurrences of a word in column B, in this initial case that is PETROL (I'm going to expand to include many others). For that match on the same row I want it to update columns 5 and 6 with descriptions. The excel spreadsheet will have hundreds of rows of entries with varying descriptions in column B.
Any help would be much appreciated.
I guess you have to run it twice because the first time you run it, the ActiveCell could be anything, and your loop depends on it not being empty to start with, but after the first run you have selected column B (and other things)...
Read this previous answer on avoiding the use of Select and Activate, it will make your code more robust: How to avoid using Select in Excel VBA macros
Revised Code
See the comments for details, here is a cleaner version of your code which should work first time / every time!
Sub m_Range_End_Method()
Dim col As Range
Dim rng As Range
Dim currentRow As Long
' Use a With block to 'Fully Qualify' the ranges to MySheet
With ThisWorkbook.Sheets("MySheet")
' Set col range to the intersection of used range and column B
Set col = Intersect(.UsedRange, .Columns("B"))
' Loop through cells in col to set description in each row
For Each rng In col
currentRow = rng.Row
' Check upper case value match against upper case string
If InStr(UCase(rng.Value), "PETROL") > 0 Then
.Cells(currentRow, 5) = "Shopping"
.Cells(currentRow, 6) = "Car"
End If
Next rng
End With
End Sub

copy data based on criteria to another sheet and clear the contents

This code is working to copy the filtered data of "Award" column marked "Yes" to another sheet; however, I'm receiving an error of "Type Mismatch." I'm not 100% now that the code is working properly to filter the data and copy correctly. I currently have 23 rows of test data for proper functionality. If I only put one row of data, then it doesn't copy and paste the data correctly. I am left with the copied 1st row of data plus the 2nd empty row of data. Additionally, it is not clearing the contents of the rows after the paste, so I may add new data as the days progress.
Sub CopySheet()
Dim i As Integer
Dim LastRow As Integer
Dim Search As String
Dim Column As Integer
Sheets("MasterData").Activate
Sheets("MasterData").Range("A1").Select
'Sets an Autofilter to sort out only your Yes rows.
Selection.AutoFilter
'Change Field:=5 to the number of the column with your Y/N.
Sheets("MasterData").Range("$A$1:$G$200000").AutoFilter Field:=7, Criteria1:="Yes"
'Finds the last row
LastRow = Sheets("MasterData").Cells(Sheets("MasterData").Rows.Count, "A").End(xlUp).row
i = 1
'Change the 3 to the number of columns you got in Sheet2
Do While i <= 11
Search = Sheets("ActiveJobStatus").Cells(1, i).Value
Sheets("MasterData").Activate
'Update the Range to cover all your Columns in MasterData.
If IsError(Application.Match(Search, Sheets("MasterData").Range("A1:G1"), 0)) Then
'nothing
Else
Column = Application.Match(Search, Sheets("MasterData").Range("A1:G1"), 0)
Sheets("MasterData").Cells(2, Column).Resize(LastRow, 1).Select
Selection.Copy
Sheets("ActiveJobStatus").Activate
Sheets("ActiveJobStatus").Cells(2, i).Select
ActiveSheet.Paste
End If
i = i + 1
Loop
'Clear all Y/N = Y
'Update the Range to cover all your Columns in MasterData.
Sheets("MasterData").Activate
Column = Application.Match("Award", Sheets("MasterData").Range("A1:F1"), 0)
Sheets("MasterData").Cells(2, Column).Resize(LastRow, 1).Select
Selection.ClearContents
End Sub
Sorry to change your code up so much, but it looks like you might be over-complicating how to do it.
This is some code from a previous question I answered where someone wanted to highlight a specific range whenever the word "Total" was found.
I changed the find to "Yes". Change the SearchRange to your column. (I think G is right).
Also, for future reference, Select should [almost never] be used.
It slows down code execution quite a bit and is not required.
I know the macro recorder likes to use it, but everything can be referenced without using select.
Brief example:
Sheets("ActiveJobStatus").Activate
Sheets("ActiveJobStatus").Cells(2, i).Select
ActiveSheet.Paste
Can Be replaced by:
Sheets("ActiveJobStatus").Cells(2, i).Paste
This code is working to copy the filtered data of "Award" column marked "Yes" to another sheet.
Sub CopyAwardsToActiveJobStatusSheet()
Dim SearchRange, First, Finder As Range
Dim PasteRow as Integer 'Add this to increment the rows we paste your data to
Set SearchRange = Sheets("MasterData").Range("G:G") 'Search This Range for "Yes"
Set Finder = SearchRange.Find("Yes") 'This is what we're looking for
If Finder Is Nothing Then Exit Sub 'We didn't find any "Yes" so we're done
'Drastically increases speed of every macro ever
'(well, when the sheets are modified at least - and it doesn't hurt)
Application.ScreenUpdating = False
First = Finder.Address 'Grab the address of the first "Yes" so we know when to stop
'Get the last row of column "A" on ActiveJobStatusSheet and start pasting below it
PasteRow = Sheets("ActiveJobStatus").Cells(Sheets("ActiveJobStatus").Rows.Count, "A").End(xlUp).Row + 1
Do
'Copy the entire row and paste it into the ActiveJobStatus sheet
'Column A and PasteRow (the next empty row on the sheet)
'You can change these if needed
Finder.EntireRow.Copy Sheets("ActiveJobStatus").Range("A" & PasteRow)
'If you just want A:G, you can use this instead:
'Finder returns the cell that contains "Yes",
'So we offset/resize to get the 6 cells before it and just copy that
'Resize doesn't like negative numbers so we have to combine:
'Finder.Offset(,-6).Resize(,7).Copy Sheets("ActiveJobStatus").Range("A" & PasteRow)
'Look for the next "Yes" after the one we just found
Set Finder = SearchRange.FindNext(after:=Finder)
PasteRow = PasteRow + 1 'Faster than looking for the end again
'Do this until we are back to the first address
Loop While Not Finder Is Nothing And Finder.Address <> First
'Clear MasterData
Sheets("MasterData").Range("A2:G" & Sheets("MasterData").UsedRange.Rows.Count).ClearContents
Application.ScreenUpdating = True 'Drastically increases speed of every macro ever.
End Sub
Just the code:
Sub CopyAwardsToActiveJobStatusSheet()
Dim SearchRange, First, Finder As Range
Dim PasteRow as Integer
Set SearchRange = Sheets("MasterData").Range("G:G")
Set Finder = SearchRange.Find("Yes")
If Finder Is Nothing Then Exit Sub
Application.ScreenUpdating = False
First = Finder.Address
PasteRow = Sheets("ActiveJobStatus").Cells(Sheets("ActiveJobStatus").Rows.Count, "A").End(xlUp).Row + 1
Do
Finder.EntireRow.Copy Sheets("ActiveJobStatus").Range("A" & PasteRow)
Set Finder = SearchRange.FindNext(after:=Finder)
PasteRow = PasteRow + 1
Loop While Not Finder Is Nothing And Finder.Address <> First
Sheets("MasterData").Range("A2:G" & Sheets("MasterData").UsedRange.Rows.Count).ClearContents
Application.ScreenUpdating = True
End Sub
Results:
MasterData Sheet:
ActiveJobStatus Sheet:

Copy and insert rows based off of values in a column

I am trying to set up a procedure that looks up cells in Column "G" and if a value is greater than 1, copy that entire table row, insert a row (as many times - 1 based on the value) and paste that value into each newly inserted row.
So if there is a quantity of 3 in cell "G4" then I would like to copy the row of that cell and insert a row below it 2 times and paste the copied values.
Below is what I have so far...
**Note all of this is in a table in Excel. (not sure if that's part the issue with my code)
Dim Qty As Range
For Each Qty In Range("G:G").cells
If Qty.Value > 1 Then
Qty.EntireRow.cell
Selection.Copy
ActiveCell.Offset(1).EntireRow.Insert
Selection.Paste
Selection.Font.Strikethrough = True
End If
Next
End Sub
There are a number of issues with your approach and code
You say the data is in an Excel Table. Use that to your advantage
When inserting rows into a range loop from the bottom up. This prevents the inserted rows interfering with the loop index
Don't use Selection (and even if you do your logic doesn't manipulate the ActiveCell)
Don't loop over the whole column (thats a million rows). Limit it to the table size
Here's a demonstration of these ideas
Sub Demo()
Dim sh As Worksheet
Dim lo As ListObject
Dim rColumn As Range
Dim i As Long
Dim rws As Long
Set sh = ActiveSheet ' <-- adjuct to suit
Set lo = sh.ListObjects("YourColumnName")
Set rColumn = lo.ListColumns("YourColumnName").DataBodyRange
vTable = rColumn.Value
For i = rColumn.Rows.Count To 1 Step -1
If rColumn.Cells(i, 1) > 1 Then
rws = rColumn.Cells(i, 1) - 1
With rColumn.Rows(i)
.Offset(1, 0).Resize(rws, 1).EntireRow.Insert
.EntireRow.Copy .Offset(1, 0).Resize(rws, 1).EntireRow
.Offset(1, 0).Resize(rws, 1).EntireRow.Font.Strikethrough = True
End With
End If
Next
End Sub

Resources