"Copy(or Cut)&Paste" operations as well as "Deleting rows" ones are expensive operations in Excel UI.
And since "Cut row" means "Delete row", then "Cut&Paste" is a most expensive one!
So the best would be avoid both!
Let's see how to do that
Avoiding Copy(or Cut)&Paste
Since you're dealing with numbers, I assume your real concern is for their values, so you can bother .Value
property of Range
object only,
rather then with cells fonts or backcolors (and all that jazz)
This means you can benefit from a very cheap statement like the following:
Range1.Value = Range2.Value
where you only have to make sure that both ranges have the same size
in your case this could be coded as:
With Range(Cells(1, iCol), Cells(rng.Columns(iCol).Rows.Count, iCol))
Cells(lastCell, 1).Resize(.Rows.Count).Value = .Value
End With
thus leaving you with all those copied values to be deleted
for this task we can make use of the .ClearContent()
method of Range
object which, again, only deals with cell .Value
property (as opposite to .Clear()
method which is much more expensive since it deals with all Range
object properties).
So that we could be tempted to just add a statement within our With
block, which is just referring to the wanted (copied) range:
With Range(Cells(1, iCol), Cells(rng.Columns(iCol).Rows.Count, iCol))
Cells(lastCell, 1).Resize(.Rows.Count).Value = .Value
.ClearContents
End With
Though syntactically correct, this approach isn't the fastest being that clearance made through many (as the columns are) statements
It's better to make a one-shot clearance:
With Cells(1,1).CurrentRegion
'
'loop code
'
Intersect(.Cells.Offset(, 1), .Cells).ClearContents '<--| clear the copied cells
End With
where you clear the cells:
belonging to the "original" CurrentRegion
of Cell(1,1)
since the With
block keeps referring to the range as it was set at that moment, ignoring subsequent changes (all those pasted values down its first column)
offsetted by 1
to avoid first column of "original" CurrentRegion
of Cell(1,1)
intersecting with itself
to avoid clearing columns out of the "original" CurrentRegion
of Cell(1,1)
Avoid Deleting Rows
But the best is yet to come!
being your data structure as per your example you can avoid pasting blank values, and so having to delete them by the end as well
just limit the values-to-be-copied range down to its last non empty row, substituting:
With Range(Cells(1, iCol), Cells(rng.Columns(iCol).Rows.Count, iCol))
with:
With Range(Cells(1, iCol), Cells(Rows.Count, iCol).End(xlUp))
In fact
rng.Columns(iCol).Rows.Count
always refers to the same rows number, namely the rows number of rng
, so that not always it could substituted with a constant but it doesn't account for the current column actual non-empty cells number
While:
Cells(Rows.Count, iCol).End(xlUp)
always follows the current column last non-empty cell
This way you won't have any blank cell copied into rng
first column and therefore no rows to delete at all!
Use With
keyword for full qualified range references
This is a golden rule for the following reasons:
avoids ranges misreferences
coding:
Worksheets("MySheetName").Cells(1, 1).CurrentRegion
makes you sure to reference the CurrentRegion
of Range
"A1" of "MySheetName" worksheet
while:
Range(Cells(1, iCol), Cells(rng.Columns(iCol))
actually has VBA refer to the active worksheet for all Range
and Cells
objects and to rng
for Columns
object.
this could still be correct and easy to follow when the code is short enough and hasn't any Select
/Selection
and/or Activate
/Active
operations, nor opens any new workbook. Otherwise all those operations would soon lead to loose the active worksheet knowledge and range reference control
Speed up code
since it avoids VBA te task to resolve a Range
reference down to its root when unnecessary
Summary
all what above may result in the following "core"-code:
Sub CombineColumns()
Dim iCol As Long
With Worksheets("MySheetName").Cells(1, 1).CurrentRegion
For iCol = 2 To .Columns.Count
With .Range(.Cells(1, iCol), .Cells(.Rows.Count, iCol).End(xlUp))
.Parent.Cells(.Parent.Rows.Count, 1).End(xlUp).Offset(1).Resize(.Rows.Count).Value = .Value
End With
Next iCol
Intersect(.Cells.Offset(, 1), .Cells).ClearContents
End With
End Sub
that you can combine with those application setting-on/offs (especially those about Calculation
) and some user information like follows:
Sub CombineColumns()
Dim iCol As Long
TurnSettings False
With Worksheets("MySheetName").Cells(1, 1).CurrentRegion
For iCol = 2 To .Columns.Count
Application.StatusBar = "Progress: " & iCol & " of: " & .Columns.Count & " (" & Format((.Columns.Count - iCol) / .Columns.Count, "Percent") & ")"
With .Range(.Cells(1, iCol), .Cells(.Rows.Count, iCol).End(xlUp))
.Parent.Cells(.Parent.Rows.Count, 1).End(xlUp).Offset(1).Resize(.Rows.Count).Value = .Value
End With
Next iCol
Intersect(.Cells.Offset(, 1), .Cells).ClearContents
End With
TurnSettings True
End Sub
Sub TurnSettings(boolSetting As Boolean)
With Application
.StatusBar = Not boolSetting
.ScreenUpdating = boolSetting
.EnableEvents = boolSetting
.Calculation = IIf(boolSetting, xlCalculationAutomatic, xlCalculationManual)
End With
End Sub
ScreenUpdating
,Events
, &Calculation
all disabled. Just how large is your data set? \$\endgroup\$