Sample WorkbookI have repeating macros that freeze with an error after running 500 to 600 times through. The number of times I need it to run will change every time but mostly be around 2000 times.Error Notice
Line of code it stops onMaE.png
The entire code is below, multiple macros running after each other and calling others until report completes. It runs fine if it runs less than 500 times.
Sub Start_New_Report()
'
' Start_New_Report Macro
' Clear Old data and prepare for new lines.
'
Application.ScreenUpdating = False
Sheets("Filtered Report").Select
Range("A2:I1048576").Select
Selection.ClearContents
Range("A2").Select
ActiveCell.FormulaR1C1 = "1"
Call Filter_Data
End Sub
Sub Filter_Data()
' Filter raw Syteline data to usable lines
Worksheets("Filtered Report").Range("B2").Value = _
Worksheets("PurchaseOrderStatus").Range("A5:E5").Value
Worksheets("Filtered Report").Range("C2").Value = _
Worksheets("PurchaseOrderStatus").Range("A6:C6").Value
Worksheets("Filtered Report").Range("D2").Value = _
Worksheets("PurchaseOrderStatus").Range("A7:F7").Value
Worksheets("Filtered Report").Range("E2").Value = _
Worksheets("PurchaseOrderStatus").Range("J5").Value
Worksheets("Filtered Report").Range("F2").Value = _
Worksheets("PurchaseOrderStatus").Range("O7").Value
Worksheets("Filtered Report").Range("G2").Value = _
Worksheets("PurchaseOrderStatus").Range("P6:R6").Value
Worksheets("Filtered Report").Range("H2").Value = _
Worksheets("PurchaseOrderStatus").Range("P7:T7").Value
Worksheets("Filtered Report").Range("I2").Value = _
Worksheets("PurchaseOrderStatus").Range("V7").Value
Call Clear_Raw_Data
End Sub
Sub Clear_Raw_Data()
' Clear Raw Data Lines
Sheets("PurchaseOrderStatus").Select
Rows("5:7").Delete
Call Blank_Cells
End Sub
Sub Blank_Cells()
' Check if blank cells exist in current line
Sheets("Filtered Report").Select
Range("B2").Select
If IsEmpty(Range("B2").Value) Then
Call Copy_Up
Else
Call Blank_Cells_Raw_Data
End If
End Sub
Sub Copy_Up()
'
' Copy Data Up from line below if cells are empty.
'
Range("B3:D3").Copy Range("B2:D2")
Call Blank_Cells_Raw_Data
End Sub
Sub Blank_Cells_Raw_Data()
Sheets("PurchaseOrderStatus").Select
Range("V5").Select
If IsEmpty(ActiveCell.Value) Then
Call Finalize_Report
Else
Call Clear_for_Next_Line
End If
End Sub
Sub Clear_for_Next_Line()
'
' Clear_for_Next_Line Macro
'
' Insert_line Macro
Sheets("Filtered Report").Select
Range("2:2").Insert CopyOrigin:=xlFormatFromRightOrBelow
' Create next index number
Worksheets("Filtered Report").Range("A2").Value = _
Worksheets("Filtered Report").Range("A3").Value + 1
Call Filter_Data
End Sub
Sub Finalize_Report()
'
' Finalize_Report Macro
' Finish report and sort the order.
'
Sheets("Filtered Report").Select
Range("A1") = "Index"
Columns("A:I").Sort key1:=Range("A2"), _
order1:=xlAscending, Header:=xlYes
End Sub
In essence, I discarded the entire model where separate subroutines were calling each other in sequence and replaced it with a single subroutine that performs all of the functions.
I opted to rewrite the sample code by removing the use of .Select (see link) and defining worksheet variables whenever possible.
One other thing I noticed was in Blank_Cells and Blank_Cells_Raw_Data, I don't think you meant to use IsEmpty there (which checks to see if a variable is initialized; see link), but rather determine if the cell itself is empty. I changed this to If Application.WorksheetFunction.CountA(Range) = 0 in both instances.
In Filter_Data, I noticed you're setting the value of one cell (e.g. B2) to the value of multiple cells (e.g. A5:E5). In testing this just set the first cell to the first value in the range defined (i.e. cell A5). Assuming you didn't mean to do something like Application.WorksheetFunction.Sum(ws2.Range("A5:E5")) (to sum the values in those cells) I just changed these to get the first cell.
I changed Filter_Data and a few other spots to use cell/column references instead of ranges when possible.
In Copy_Up I replaced the .Copy function with actually setting the cells to the values (Copy can get weird sometimes so I avoid using it whenever possible).
Additionally, since .Delete and .Insert both slow down the macro considerably, I used a method that avoids doing either by just checking one group of three rows on 'PurchaseOrderStatus' at a time then moving to the next one, and by writing to the first free row on 'Filtered Report' instead of inserting new rows at the top. This sped the macro up considerably (~35 seconds to less than a second).
Option Explicit
Sub Start_New_Report()
Dim ws1 As Worksheet, ws2 As Worksheet
Dim rng As Range
Dim newRow As Long, lastRow As Long, x As Long
Set ws1 = ThisWorkbook.Sheets("Filtered Report")
Set ws2 = ThisWorkbook.Sheets("PurchaseOrderStatus")
' Turn screen updating / calculation off for speed
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' Clear Old data and prepare for new lines.
ws1.Range(ws1.Cells(2, 1), ws1.Cells(10000, 9)).ClearContents
ws1.Cells(2, 1) = 1
' Define last row
lastRow = ws2.Cells(ws2.Rows.Count, 1).End(xlUp).Row - 2
' Iterate through all groups of 3 rows on PurchaseOrderStatus sheet
For x = 5 To lastRow Step 3
' Determine new row to write to
newRow = ws1.Cells(ws1.Rows.Count, 1).End(xlUp).Row
' Filter raw Syteline data to usable lines
ws1.Cells(newRow, 2) = ws2.Cells(x, 1)
ws1.Cells(newRow, 3) = ws2.Cells(x + 1, 1)
ws1.Cells(newRow, 4) = ws2.Cells(x + 2, 1)
ws1.Cells(newRow, 5) = ws2.Cells(x, 10)
ws1.Cells(newRow, 6) = ws2.Cells(x + 2, 15)
ws1.Cells(newRow, 7) = ws2.Cells(x + 1, 16)
ws1.Cells(newRow, 8) = ws2.Cells(x + 2, 16)
ws1.Cells(newRow, 9) = ws2.Cells(x + 2, 22)
' Copy Data Up from line below if cells are empty.
If Application.WorksheetFunction.CountA(ws1.Cells(newRow, 2)) = 0 Then
ws1.Cells(newRow, 2) = ws1.Cells(newRow - 1, 2)
ws1.Cells(newRow, 3) = ws1.Cells(newRow - 1, 3)
ws1.Cells(newRow, 4) = ws1.Cells(newRow - 1, 4)
End If
' Create next index number if not the last row
If x <> lastRow Then
ws1.Cells(newRow + 1, 1) = ws1.Cells(newRow, 1).Value + 1
End If
Next x
' Finish report and sort the order.
ws1.Range(ws1.Columns(1), ws1.Columns(9)).Sort _
Key1:=ws1.Cells(2, 1), _
Order1:=xlAscending, _
Header:=xlYes
' Turn screen updating / calculation back on
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Related
I have a file with more then 1 sheet, where in the Reports Sheet I want to filter by ASBN products and then delete them, because I already processed it in another sheet, so I need to delete the initial ones in order to paste back the processed one.
Idea is that this deleting code which is working, but is taking for at least 20 minutes, because I want to delete 123 572 rows, do you have any idea how could I make this work faster?
I also tried to clear contents first and then to delete empty rows, but it's the same.
Here you find the code:
Public Sub Remove_ABSN()
Dim area As String
Dim start As Long
area = "ABSN"
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
Application.EnableEvents = False
start = Worksheets("Reports").Cells(Cells.Rows.Count, 1).End(xlUp).Row
Worksheets("Reports").Range("$A$2:$AN" & start).AutoFilter Field:=8, Criteria1:=area, Operator:=xlFilterValues
Worksheets("Reports").Range("$A$2:$AN$" & start).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Sheets("Reports").ShowAllData
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.DisplayAlerts = True
Application.EnableEvents = True
End Sub
I think AutoFilter will be the fastest way to do it. Here are two sample scripts to try. You can see for yourself which one is faster.
Public Sub UnionDeleteRowsFast()
' Careful...delete runs on Sheet1
Dim sh2 As Worksheet
Set sh2 = Sheets("Sheet1")
Dim lastrow As Long
Dim Rng As Range
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
For i = lastrow To 2 Step -1
If Cells(i, 2).Value = "Delete" Then
If Rng Is Nothing Then
Set Rng = Range("B" & i)
Else
Set Rng = Union(Rng, Range("B" & i))
End If
End If
Next
If Not Rng Is Nothing Then Rng.EntireRow.Delete
End Sub
Sub AutoFilterDeleteRowsFast()
' Careful...delete runs on ActiveSheet
With ActiveSheet
.AutoFilterMode = False
With Range("B4", Range("B" & Rows.Count).End(xlUp))
.AutoFilter 1, "*Delete*"
On Error Resume Next
.Offset(1).SpecialCells(12).EntireRow.Delete
End With
.AutoFilterMode = False
End With
End Sub
There is a way that is much faster.
Suppose a table of 100,000 lines (A1:B100001) with headers in line 1. Then delete condition refers to just 1 column (B).
One needs a auxiliar column (A) just to count the lines in the original order. Here I use autofill function.
So one can sort the table and after restore the original order.
Below there is a complete example, that generates randomly numbers from 1 to 10 (it's slow!), and after quickly delete all lines with values 3
Sub EraseValue()
Application.ScreenUpdating = False
Dim i As Long
Dim T1 As Single ' milisecs after booting (Start)
Dim T2 As Single ' milisecs after booting (End)
Dim LIni As Variant ' Initial line to delete
Dim LEnd As Variant ' Final line to delete
Const Fin = 100000 ' Lines in the table
Const FinStr = "100001" ' Last line (string)
Randomize (GetTickCount()) ' Seed of random generation
For i = 1 To Fin
Cells(i + 1, "B") = Int(Rnd() * 10 + 1) ' Generates from 1 to 10
If i Mod 100 = 0 Then Application.StatusBar = i
DoEvents
Next i
Application.StatusBar = False
Dim Table As Range
Dim Colu As Range
T1 = GetTickCount() ' Initial time
Cells(2, "A") = 1 ' Starting value
Cells(3, "A") = 2 ' Step
' Fill from 1 to 100,000 step 1
Range("A2:A3").AutoFill Destination:=Range("A2:A" & FinStr)
' Order by condition column
Table.Sort Key1:=Cells(1, "B"), Header:=xlYes
'One needs delete lines with column B = 3
'LIni: Search key that not exceed value 2 in the column
' (2 is immediately previous value)
'LEnd: Search key that not exceed value 3 in the column
'LIni and LFim is relative to 2 so add 1 for skip the header
'Add more 1 to Lini in order to get the first value in the column >= key
'
LIni = Application.Match(2, Colu, 1) + 2
LEnd = Application.Match(3, Colu, 1) + 1
If IsError(LIni) Or IsError(LEnd) Or LEnd < LEnd Then
MsgBox ("There is no lines to delete")
End
End If
Range(Rows(LIni), Rows(LEnd)).Delete (xlUp) ' Delete lines
Table.Sort Key1:=Cells(1, "A"), Header:=xlYes ' Restore initial order
T2 = GetTickCount() ' Get the final time
MsgBox ("Elapsed milisecs: " + Format((T2 - T1), "0"))
End Sub
In my old computer, it take a little bit more that 0.5 secs with 100,000 lines.
If one has a condition that involves 2 columns or more, one need to create an another auxiliary column with a formula that concatenate these columns related do desired condition and run the match in this column. The formula needs to usage relative references. For instance (assuming that the data of column C are string and is already filled with a header).
Cells(1,4) = "NewCol" ' New column D
Dim NewCol As Range
Set NewCol = Range("D2:D" & FinStr)
' Two previous columns concatenated. In line 2
' the formula would be "=Format(B2,"0")+C2" (B2 is a number)
NewCol.FormulaR1C1 = "=Format(RC[-2],"0") & RC[-1]"
NewCol.Copy
NewCol.PasteSpecial(XlValues) ' Convert all formulas to values
Application.CutCopyMode=false
So one usages the column D instead column B
I have an Excel-macro that basically works just fine for most of the cases, but there are three issues that bug me.
The code is a bit longer so I've reduced it to address the issues: (The issues are also marked in my code.)
Nr.1: When uniqueArray consists of more than one entry, the Dim for item and uniqueArray are fine. But when I've tested the unlikely case that uniqueArray consists of only one entry, I got the error, that the types don't match. I'm usally not programming stuff in Excel, so I'm not really familiar with the different types in vba. Do I need arrays here or can I just change the Dim?
Nr.2: The code gets slower and slower, the more sheets are added to the workbook by the macro. Is that normal behaviour, or can I speed up my code a bit?
Nr.3: A few years ago I had an issue with a slow macro. Then I found the hint with a forced pause. I've tried it with this macro again, and it improved the speed by a huuuge amount of time. How come a pause speeds up a macro?
Sub Three_Issues()
Dim ColumnLetter As String
Dim cell As Range
Dim sheetCount, TotalRow, TotalCol As Integer
'Dim item, uniqueArray As Variant
Dim item, uniqueArray() As Variant
Dim lastRow As Long
Application.ScreenUpdating = False
'Get unique brands:
With Sheets("Brand")
.Columns(1).EntireColumn.Delete
Sheets("Sales").Columns("R:R").AdvancedFilter Action:=xlFilterCopy, CopyToRange:=.Range("A1"), Unique:=True
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
'uniqueArray = .Range("A3:A" & lastRow)
'Update:
If .Range("A3:A" & lastRow).Cells.Count = 1 Then
ReDim uniqueArray(1, 1)
uniqueArray(1, 1) = .Range("A3")
Else
uniqueArray = .Range("A3:A" & lastRow).Value
End With
TotalRow = Sheets("Sales").UsedRange.Rows.Count
TotalCol = Sheets("Sales").UsedRange.Columns.Count
ColumnLetter = Split(Cells(1, TotalCol).Address, "$")(1) 'Num2Char
sheetCount = 0 'Counter for statusbar
For Each item In uniqueArray 'item=Brand
'->Issue 1: Runtimer error 13 Types don't match: This happens if the uniqueArray consists of only one brand.
'Then item is Variant/Empty and uniqueArray is Variant/String
'If uniqueArray consists of more than one brand - which is usually the case - it works fine.
'item=Variant/Empty uniqueArray=e.g. Variant/Variant(1 to 2, 1 to 1)
'Can I change the Dim statement to solve this special case, or do I need arrays maybe?
'Filter sales for each brand:
With Sheets("Sales")
.Range(.Cells(2, 1), .Cells(TotalRow, TotalCol)).AutoFilter Field:=18, Criteria1:=item
End With
With Sheets("Agents")
'Delete old...
.Range(.Cells(2, 1), .Cells(2, 1).End(xlDown)).Clear
'...and get new
Sheets("Sales").Range(Sheets("Sales").Cells(3, 2), Sheets("Sales").Cells(2, 2).End(xlDown)).SpecialCells(xlCellTypeVisible).Copy
.Range("A2").PasteSpecial Paste:=xlPasteValues
Application.CutCopyMode = False
End With
'List with all agents
For Each cell In Worksheets("Agents").Range("A2", Worksheets("Agents").Range("A1").End(xlDown))
With Sheets("Report")
.Range("I4") = cell 'Copy agent and update the formulas within the report
'->Issue 2: It takes around 10 seconds to fill 10 sheets with the reports of 10 agents.
'When I reach 70-80 sheets, it slows down to 30 seconds for 10 sheets.
'Is this just because of the number of sheets, or can I speed it up again?
.Range(.PageSetup.PrintArea).Copy
Sheets.Add After:=Sheets("Report")
Selection.PasteSpecial Paste:=xlPasteAllUsingSourceTheme, Operation:=xlNone _
, SkipBlanks:=False, Transpose:=False
Selection.PasteSpecial Paste:=xlPasteColumnWidths, Operation:=xlNone, _
SkipBlanks:=False, Transpose:=False
ActiveSheet.UsedRange.Value = ActiveSheet.UsedRange.Value 'Replace all formulas with values
Application.CutCopyMode = False
ActiveSheet.Name = cell
sheetCount = sheetCount + 1
If sheetAnz Mod 10 = 0 Then Application.StatusBar = sheetAnz 'Get statusupdate every 10 sheets
End With
Next
'->Issue 3: I create up to 400 sheets and when I want to continue and do some sorting of the sheets for example it takes a very long time.
'But if I add this break for a second, it works reasonably fine again. Why is that? Does vba needs the break to catch up with itself?
'Since the issue is not the sorting and the other stuff after the pause.
Application.Wait (Now + TimeValue("0:00:01")) 'Code becomes faster after that...
'Continue with other stuff.... sorting sheets and so on
Next
Application.ScreenUpdating = True
End Sub
Any ideas on one of the issues?
You can output a array with 1 value or multiple values using the below UDF. This would benefit from also passing along a worksheet variable so objects can be properly qualified
Call the function from your current macro like so
uniqueArray = MyArr(lastrow)
Public Function MyArr(lastrow As Long) As Variant
If Range("A3:A" & lastrow).Cells.Count = 1 Then
ReDim MyArr(1, 1)
MyArr(1, 1) = Range("A3")
Else
MyArr = Range("A3:A" & lastrow).Value
End If
End Function
I have a VBA that selects specific columns to create a chart. I was having an issue where certain columns would be omitted from the chart and I didn't know why. After troubleshooting, I found that once the omitted columns were converted from Text to Column that they worked. Any idea why?
I have tried to convert every column from Text to Column using a VBA but I get an error
...can only convert one column at a time...
Doing one at a time would take forever as I have hundreds of columns to do. Is there a VBA that can quickly process this?
Here is my code for creating the charts if it helps:
Sub Graph2()
' Graphs for monitoring
Dim my_range As Range, t, co As Shape
t = Selection.Cells(1, 1).Value & " - " & ActiveSheet.Name
Dim OldSheet As Worksheet
Set OldSheet = ActiveSheet
Set my_range = Union(Selection, ActiveSheet.Range("A:A"))
Set co = ActiveSheet.Shapes.AddChart2(201, xlLine) 'add a ChartObject
With co.Chart
.FullSeriesCollection(1).ChartType = xlXYScatter
.FullSeriesCollection(1).AxisGroup = 1
.FullSeriesCollection(2).ChartType = xlLine
.FullSeriesCollection(2).AxisGroup = 1
.SetSourceData Source:=my_range
'highlight final dot of data
.FullSeriesCollection(1).Points(.FullSeriesCollection(1).Points.Count - 1).ApplyDataLabels Type:=xlShowValue
.HasTitle = True
.ChartTitle.Text = t
'ResolveSeriesnames co.Chart
.Location Where:=xlLocationAsObject, Name:="Graphs"
End With
OldSheet.Activate
End Sub
Here is my answer.
Purpose:
Take a list of columns and apply the Range.TextToColumns method one by one as fast as possible.
Algorithm:
1. Create an array of needed columns;
2. Go through this array column by column and:
- 2.1 Check whether there is any data to the right;
- 2.2 Make sure to insert enough columns to preserve data on the right;
- 2.3 Apply Range.TextToColumns method.
Tested on:
Range of 200 rows and 200 columns filled with "Sample Data" text and randomly inserted "Sample Data Data Data Data Data" text to test with different delimiters quantity. Used space as delimiter:
Code:
Sub SplitColumns()
Dim rToSplit() As Range, r As Range
Dim i As Long, j As Long, k As Long
Dim sht As Worksheet
Dim delimiter As String
Dim consDelimiter As Boolean
Dim start As Single, total As Single
Dim delimitersCount() As Long
'========================== TESTING STUFF =======================================
' set working sheet
Set sht = ThisWorkbook.Sheets("Sheet2")
' re-create sample data (it is changed on each macro run)
sht.Cells.Clear
ThisWorkbook.Sheets("Sheet2").Cells.Copy Destination:=sht.Cells(1, 1)
' timer for testing purposes - start point
start = Timer
'======================== END OF TESTING STUFF ===================================
' Set the delimiter
' I've used space
delimiter = " "
' assign a ConsecutiveDelimiter state
consDelimiter = False
Application.ScreenUpdating = False
'=================== CREATING A LIST OF COLUMNS FOR SPLIT ========================
' create an array of columns to be changed
' at this sample I take all 200 columns
' you have to assign your own range which is to be splitted
With sht
For i = .Cells(1, Columns.Count).End(xlToLeft).Column To 1 Step -1
' add columns to an array
If Not .Cells(1, i) = "" Then
ReDim Preserve rToSplit(j)
Set rToSplit(j) = Range(.Cells(1, i), .Cells(Rows.Count, i).End(xlUp))
j = j + 1
End If
Next
End With
'=============== END OF CREATING A LIST OF COLUMNS FOR SPLIT ======================
'============================= PERFORMING SPLIT ===================================
' go through each column in array
' from left to right, because
' there may be a need to insert columns
For j = LBound(rToSplit) To UBound(rToSplit)
' check whether there is any data on the right from the top cell of column
' note - I'm checking only ONE cell
If Not rToSplit(j).Cells(1, 1).Offset(0, 1) = "" Then
' creating another array:
' purpose - check cells in column
' and count quantity of delimiters in each of them
' quantity of delimiters = quantity of columns to insert
' in order not to overwrite data on the right
For Each r In rToSplit(j).Cells
ReDim Preserve delimitersCount(k)
delimitersCount(k) = UBound(Split(r.Text, delimiter))
k = k + 1
Next
' get the maximun number of delimiters (= columns to insert)
For i = 1 To WorksheetFunction.Max(delimitersCount)
' and insert this quantity of columns
rToSplit(j).Cells(1, 1).Offset(0, 1).EntireColumn.Insert
Next
' split the column, nothing will be replaced
rToSplit(j).TextToColumns Destination:=rToSplit(j).Cells(1, 1), ConsecutiveDelimiter:=consDelimiter, Tab:=False, Semicolon:=False, Comma:=False, _
Space:=False, Other:=True, OtherChar:=delimiter
Else
' here I just split column as there is no data to the right
rToSplit(j).TextToColumns Destination:=rToSplit(j).Cells(1, 1), ConsecutiveDelimiter:=consDelimiter, Tab:=False, Semicolon:=False, Comma:=False, _
Space:=False, Other:=True, OtherChar:=delimiter
End If
' clear the delimiters count array
Erase delimitersCount
' go to next column
Next
' done
'========================= END OF PERFORMING SPLIT ===================================
' timer for testing purposes - time difference in seconds
total = Timer - start
Debug.Print "Total time spent " & total & " seconds."
Application.ScreenUpdating = True
End Sub
Hope that helps.
Can anyone help me adjust this code to fix my solution?
I have a button that adds x amount of new rows from A5 downwards. Columns A - Z.
I would like the new rows to be blank but still contain dropdowns and formula. New to VBA and struggling with this one.
I think I need to change the range and add xlPasteFormulas but unsure where and how for both. Any help hugely appreciated.
Option Explicit
Sub AddRows()
Dim x As Integer
x = InputBox("How many rows would you like to add?", "Insert Rows")
'Selecting range to insert new cells
Range(Cells(5, 1), Cells(x + 4, 1)).EntireRow.Insert
'Copys current cell A6 and past in the new cells
Cells(x + 5, 1).Copy Range(Cells(5, 1), Cells(x + 4, 1))
'if you want the cells to be blank but still have the drop down options
Range(Cells(5, 1), Cells(x + 4, 1)).ClearContents
End Sub
Please try the code below. It will copy everything from the BaseRow and then delete constant values in that range, leaving formats, including data validations, and formulas.
Sub AddRows()
Const BaseRow As Long = 11 ' modify to suit
Dim x As String ' InputBox returns text if 'Type' isn't specified
Dim Rng As Range
Dim R As Long
x = InputBox("How many rows would you like to add?", "Insert Rows")
If x = "" Then Exit Sub
R = BaseRow + CInt(x) - 1
Rows(BaseRow).Copy 'Copy BaseRow
'specify range to insert new cells
Set Rng = Range(Cells(BaseRow, 1), Cells(R, 1))
Rng.Insert Shift:=xlDown
' insert the new rows BEFORE BaseRow
' to insert below BaseRow use Rng.Offset(BaseRow - R)
Set Rng = Rng.Offset(BaseRow - R - 1).Resize(Rng.Rows.Count, ActiveSheet.UsedRange.Columns.Count)
Rng.Select
On Error Resume Next
Rng.SpecialCells(xlCellTypeConstants).ClearContents
Application.CutCopyMode = False '
End Sub
The code now has an emergency exit: If you don't enter anything in the InputBox the procedure terminates. Note that new rows are inserted above the BaseRow. After the insertion all new rows and the old row are identical. You can then choose to retain the constants in either the first or the last of these rows, effectively meaning, insert new, blank rows either above or below the BaseRow.
I have a rather large sheet (approx 60K rows by 50 cols). I'm trying to copy several (2 to 8) rows into clipboard and then insert copied cells. This operation takes more than a minute to complete!
I've tried disabling automatic calculations, initiating this operation from VBA, like this:
Range("A1").Insert xlShiftDown
to no available. If I paste (Ctrl-V) rather than insert it works like a snap.
Any ideas how to work around this issue?
Since you can paste the data quickly enough use that instead of inserting, then sort the rows:
In an empty column on the first row of data type the number of rows you want to insert plus 1 (e.g. to insert 3 rows type 4)
Add the next number in the next row, then select both cells and autocomplete the column so that each row has an increasing number
Paste the new data at the end of the old data, immediately after the last row
Number the first row pasted as 1, the 2nd as 2 etc
Sort the sheet ascending on the number column then delete the column
I implemented Absinthe's algorithm, here's the code:
Sub InsertRows()
Dim SourceRange, FillRange As Range
Dim LastCol, LastRow, ActRow, CpdRows As Long
Dim i As Integer
If Application.CutCopyMode <> xlCopy Then Exit Sub
Application.ScreenUpdating = False
Application.EnableEvents = False
With ActiveSheet
LastCol = .UsedRange.Columns.Count
LastRow = .UsedRange.Rows.Count
ActRow = ActiveCell.Row
.Paste Destination:=.Cells(LastRow + 1, 1)
CpdRows = .UsedRange.Rows.Count - LastRow
Application.Calculation = xlCalculationManual
Set SourceRange = .Range(.Cells(ActRow, LastCol + 1), .Cells(ActRow + 1, LastCol + 1))
SourceRange.Cells(1).Value = CpdRows + 1
SourceRange.Cells(2).Value = CpdRows + 2
Set FillRange = .Range(.Cells(ActRow, LastCol + 1), .Cells(LastRow, LastCol + 1))
SourceRange.AutoFill Destination:=FillRange
For i = 1 To CpdRows
.Cells(LastRow + i, LastCol + 1).Value = i
Next i
.Range(.Cells(ActRow, 1), .Cells(LastRow + CpdRows, LastCol + 1)).Sort Key1:=.Cells(ActRow, LastCol + 1), _
Order1:=xlAscending, Header:=xlNo, MatchCase:=False, Orientation:=xlTopToBottom
.Columns(LastCol + 1).Delete
End With
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
It's works definitely faster than "insert copied cells" and it seems it's accelerating after the 1st use (I mean, when I run the macro for the 2nd, 3rd etc time it works even faster than on the 1st run). There are the cons, too. For example, named ranges do not automatically expand when you insert the lines in this manner.
And the most significant problem of this method: Excel does not move the borders with the cells when sorting. Therefore, the border structure will be ruined. The only workaround I know of is to use conditional formatting for the borders.
This all being said, it's a good workaround