Subscript out of range - vba

I'm reaching out because on a works system computer I get run-time errors on basic macros (Excel 2010) Windows Operating System. These errors don't occur on my home Excel 2010, or 2016 systems. I should NOT be getting subscript out of range errors when performing the code in new files.
I wrote these on my own computer without any problems.
Option Explicit
Sub MoveDataOtherSheets()
With Excel.ThisWorkbook.Sheets("Sheet3")
Dim cell
For Each cell In .Range(.Cells(2, 1), Cells(.Rows.Count, 1).End(Excel.xlUp))
If cell(1, 1) = "PERSONAL" Then
With Excel.ThisWorkbook.Sheets("Sheet4")
cell.EntireRow.Copy .Cells(.Rows.Count, 1).End(Excel.xlUp)(2, 1)
End With
End If
If cell(1, 1) = "COMPANY" Then
With Excel.ThisWorkbook.Sheets("Sheet5")
cell.EntireRow.Copy .Cells(.Rows.Count, 1).End(Excel.xlUp)(2, 1)
End With
End If
Next
End With
End Sub

As I commented I think the only possible causes of that error are:
any of the worksheet names you are using is not valid for the actual workbook the running macro resides in (which is ThisWorkbook exactly)
the missing dot in the 2nd occurrence of Cells in For Each cell In .Range(.Cells(2, 1), Cells(.Rows.Count, 1).End(Excel.xlUp)) statements
thus while all other references (.Range and .Cells) are parented to the object after the last With keyword, the simple Cells(.Rows.Count, 1) actually refers to the currently active worksheet column 1 last row
but I mostly wrote this answer to propose you some refactoring of your code:
Option Explicit
Sub MoveDataOtherSheets()
Dim cell As Range, dataRng As Range
Dim shtName As String
With Excel.ThisWorkbook
Set dataRng = SetDataRng(.Worksheets("Sheet3"), 1)
For Each cell In dataRng
shtName = GetSheetName(cell)
If shtName <> "" Then CopyRow cell, .Sheets(shtName)
Next cell
End With
End Sub
Sub CopyRow(cell As Range, sht As Worksheet)
With sht
cell.EntireRow.Copy .Cells(.Rows.Count, 1).End(Excel.xlUp)(2, 1)
End With
End Sub
Function SetDataRng(sht As Worksheet, colIndex As Long) As Range
With sht
Set SetDataRng = .Range(.Cells(2, colIndex), .Cells(.Rows.Count, colIndex).End(Excel.xlUp))
End With
End Function
Function GetSheetName(cell As Range) As String
Select Case cell
Case "PERSONAL"
GetSheetName = "Sheet4"
Case "COMPANY"
GetSheetName = "Sheet5"
Case Else
GetSheetName = ""
End Select
End Function
which takes the move from the following considerations:
it's preferable not to nest With blocks referring not correlatives objects
in
With Excel.ThisWorkbook.Sheets("Sheet3")
...
With Excel.ThisWorkbook.Sheets("Sheet4")
you're nesting two not-parented With objects, thus breaking a sane With "chain" like could be the following:
With Excel.ThisWorkbook
With .Sheets("Sheet3")
...
End With
With .Sheets("Sheet4")
...
End With
End With
I'm not telling you exactly why this "chain" breaking is insane since I learned it quite a long time ago but can't recall now (!), but it has to do with memory management
avoid putting into With blocks code that doesn't use the referenced object
like that Dim cell was
those two If blocks are clearly mutually exclusive: a cell value can't equal "PERSONAL" and "COMPANY" at the same time!
so the correct If block would be:
If cell(1, 1) = "PERSONAL" Then
....
ElseIf cell(1, 1) = "COMPANY" Then
...
End If
but at this point I'd suggest you to switch to the Select Case block construct, which is more suitable for further code branching (should more comparing values arise) and more readable
it's a good coding habit to demand specific tasks to dedicated Functions/Subs
this holds true even if these tasks are up to now simple and short coded, since the code will always grow and quickly become cluttered and hardly comprehensible and maintanable
both the refactoring reasons and the refactored coded are a contribution of mine and are much likely to be improved
but they can be a good starting point

Related

Replace cell values in specific sheets with defined name

I am trying to run some code that replaces the cell values in a specific column with a defined name. In addition, I have a condition that the replacement should only take place if the first 9 characters of the values are xxxxxxxxx.
More precisely, it should change the values in C:C in 2 specific worksheets (I don't want to loop through the whole workbook).
I am not sure why nothing happens in the code (no error messages, nothing).
I presume, however, that I should not use With if I want the code to work in these 2 specific worksheets. I am also aware that my use of Range is probably not totally correct.
Sub ChangeMe()
Dim cl As Range
For Each cl In Worksheets("Sheet1").Range("C:C").End(xlUp)
With Worksheets("Sheet2").Range("C:C").End(xlUp)
If Left(cl.Value, 9) = "XXXXXXXXX" Then
cl.Value = ThisWorkbook.Names("MyDefinedName").RefersToRange
End If
End With
Next cl
End Sub
In answer your original questions:
I am not sure why nothing happens in the code (no error messages, nothing).
Nothing happens because your worksheet values are lowercase xxxxxxxxx, whilst your code checks for uppercase XXXXXXXXX.
I presume, however, that I should not use With if I want the code to work in these 2 specific worksheets.
Actually, you can use With with multiple sheets, as I will demonstrate below.
I am also aware that my use of Range is probably not totally correct.
That is true. If you were to fix the uppercase issue, only C1 would be changed. This is because .End() works on a single cell. If you supply a multi-cell range, it uses the top left most cell. So .Range("C:C").End(xlUp) is equivalent to .Range("C1").End(xlUp) which evaluates to just C1.
The following will answer your updated question:
Option Explicit
Public Sub ChangeMe()
Const l_xxxxxxxxx As String = "xxxxxxxxx"
Const l_MyDefinedName As String = "MyDefinedName"
Const s_Delimiter As String = ","
Const s_WorkSheetNames As String = "Sheet1,Sheet2"
Const s_ColumnToChange As String = "C:C"
Dim varWorkSheetName As Variant
For Each varWorkSheetName In Split(s_WorkSheetNames, s_Delimiter)
With Worksheets(varWorkSheetName).Range(s_ColumnToChange)
Dim rngCell As Range
For Each rngCell In .Resize(.Cells(Rows.Count).End(xlUp).Row)
With rngCell
Dim strCellValue As String: strCellValue = .Value2
If Left(strCellValue, Len(l_xxxxxxxxx)) = l_xxxxxxxxx Then
.Value2 _
= Names(l_MyDefinedName).RefersToRange.Value2 _
& Right$(strCellValue, Len(strCellValue) - Len(l_xxxxxxxxx))
End If
End With
Next rngCell
End With
Next varWorkSheetName
End Sub
Notes:
It is a good idea to use constants so all literal values are typed once only and kept grouped together.
Using .Value2, instead of .Value, is the recommended way to access a cell's value as it avoids implicit casting and is therefore faster. (Using .Value can also sometimes cause issues.)
Surprisingly, in VBA there are good reasons to put a variable declaration as close as possible to the first use of the variable. Two such reasons are 1) it improves readability, and 2) it simplifies future refactoring. Just remember that the variable is not reinitialised every time the Dim is encountered. Initialisation only occurs the first time.
If I understood your post correctly (which I doubt it), I think you want to loop through column "C" in both "Sheet1" and "Sheet2". Every cell that starts with 9 "XXXXXXXXX", should be replaced with the value in "MyDefinedName" Named Range.
Code
Option Explicit
Sub ChangeMe()
Dim cl As Range
Dim sht As Worksheet
For Each sht In ThisWorkbook.Sheets
With sht
If .Name = "Sheet1" Or .Name = "Sheet2" Then
For Each cl In .Range("C1:C" & .Cells(.rows.Count, "C").End(xlUp).Row)
If Left(cl.Value, 9) = "XXXXXXXXX" Then
cl.Value = ThisWorkbook.Names("MyDefinedName").RefersToRange
End If
Next cl
End If
End With
Next sht
End Sub
Let's imagine that this is your input:
In this case, you want to change the values in range A1:A2 to the value in C1 (named range xxxx123), because it starts with xxxx123. This is the code to achieve it:
Public Sub TestMe()
Dim myCell As Range
Dim myNamedRange As String
myNamedRange = "xxxx123"
For Each myCell In Range("A1:A2")
If Left(myCell, Len(myNamedRange)) = myNamedRange Then
myCell.Value = Range(myNamedRange)
End If
Next myCell
End Sub

Efficiency through functions, I am lost

I struggle with VBA - I tend to write as though I am the Macro Recorder and as a result write really ugly macros and end up making things far more complicated than needs be.
Can you possibly have a look and help identify some efficiencies? I want to learn to write good code, but need to compare and contrast and its hard from looking at other peoples examples.
Sub ColumnSearch()
'Filepath variables - the filename changes daily but always contains the name notifications, this seemed to be the easiest method
Dim filepath As String
filepath = ActiveWorkbook.Path + "\"
Application.ScreenUpdating = False
Dim file As String
Dim fullfilepath As String
file = Dir$(filepath & "*Notifications*" & ".*")
fullfilepath = filepath & file
Application.EnableEvents = False
Workbooks.Open (fullfilepath)
Windows(file).Activate
Application.EnableEvents = True
'variables set as string and range respetively
Dim strDoN As String, strOffice As String, strARN As String, strPIN As String, strAN As String, strAT As String, strSoS As String
Dim rngDoN As Range, rngOffice As Range, rngARN As Range, rngPIN As Range, rngAN As Range, rngAT As Range, rngSoS As Range
Dim rng2DoN As Range, rng2Office As Range, rng2ARN As Range, rng2PIN As Range, rng2AN As Range, rng2AT As Range, rng2SoS As Range
Dim myRange As Range
Dim NumCols, i As Integer
'str variables set as the text in row 1 (title cells)
strDoN = "Date of Notification"
strOffice = "Office Centre"
strARN = "Appeal Reference Number"
strPIN = "PIN"
strAN = "Appellant Name"
strAT = "Appeal Type"
strSoS = "SoS Decision Date"
Sheets("New Appeals").Activate
'For loop to find the address of the strings above
For i = 1 To 11
Select Case Cells(1, i).Value
Case strDoN
Set rngDoN = Cells(1, i) '
Case strOffice
Set rngOffice = Cells(1, i)
Case strARN
Set rngARN = Cells(1, i)
Case strPIN
Set rngPIN = Cells(1, i)
Case strAN
Set rngAN = Cells(1, i)
Case strAT
Set rngAT = Cells(1, i)
Case strSoS
Set rngSoS = Cells(1, i)
Case Else
'no match - do nothing
End Select
Next i
'Identify the count of cells to be copied from one sheet to the other
RowLast = Cells(Rows.Count, rngOffice.Column).End(xlUp).Row
Cells(RowLast - 1, rngOffice.Column).Select
Range(Selection, Selection.End(xlUp)).Offset(1, 0).Copy
'activate the other workbook, run the same search for loop but with rng2 being set (rng and rng2 can be different as there are sometimes extra columns that are not required
Workbooks("Book2.xlsm").Activate
Sheets("New Appeals").Select
For i = 1 To 11
Select Case Cells(1, i).Value
Case strDoN
Set rng2DoN = Cells(1, i) '<~~ set the range object to this cell
Case strOffice
Set rng2Office = Cells(1, i)
Case strARN
Set rng2ARN = Cells(1, i)
Case strPIN
Set rng2PIN = Cells(1, i)
Case strAN
Set rng2AN = Cells(1, i)
Case strAT
Set rng2AT = Cells(1, i)
Case strSoS
Set rng2SoS = Cells(1, i)
Case Else
'no match - do nothing
End Select
Next i
Dim RowLast2 As Long
'find the last cell that was updated (every day the list will grow, it has to be pasted at the bottom of the last update)
RowLast2 = Cells(Rows.Count, rng2Office.Column).End(xlUp).Row
Cells(RowLast2, rng2Office.Column).Offset(1, 0).Select
Selection.PasteSpecial
Workbooks(file).Activate
Sheets("New Appeals").Select
'start from scratch again but with the next variable etc
RowLast = Cells(Rows.Count, rngARN.Column).End(xlUp).Row
Cells(RowLast - 1, rngARN.Column).Select
Range(Selection, Selection.End(xlUp)).Offset(1, 0).Copy
Workbooks("Book2.xlsm").Activate
Sheets("New Appeals").Select
RowLast2 = Cells(Rows.Count, rng2ARN.Column).End(xlUp).Row
Cells(RowLast2, rng2ARN.Column).Offset(1, 0).Select
Selection.PasteSpecial
Workbooks(file).Activate
Sheets("New Appeals").Select
End Sub
If this is inapropriate let me know and I'll delete it if needed!
I would consider the following:
Macro description: The comments below the subroutine header should be concise and explain what the macro does, if it is not clear from its name. Your subroutine searches columns. You might want to include what is searched, i.e., "searches a predefined set of strings, selects [...] and copies from [...] to [...]. I would avoid details such as "this seemed to be the easiest method".
Notation / Variable names: It is good practice to give consistent names to your variables. In VBA CamelCase is commonplace. Also, prepending the object type in the variable name is very common, i.e., strDoN as String. If you do so though, make sure you do it everywhere (so filepath should be strFilePath). See here for the naming conventions.
Type declaration: Place all Dim statements at the beginning of the subroutine.
Events: Be careful with enabling and disabling events. If you disable events they won't be re-enabled automatically, so in case of an error (exception) there should be additional actions that re-enable the events. See this post for details on error handling.
As Chris Neilsen mentioned in the comments, avoid using Select and Activate.
Proper Dim-ing: When you do Dim NumCols, i as Integer you actually do Dim NumCols as Variant, i as Integer. If you want both of them to be integers, use Dim Numcols as Integer, i as Integer. Also, prefer Long to Integer.
Explicit Declarations: Put Option Explicit on top of your modules/worksheets. This means that every variable that is used should have been declared first (with a Dim statement). This is useful to avoid bugs from typos, and to have all your variables defined in a single place. Some of your variables, such as RowLast are not defined explicitly now, and they are of Variant type, while their type could had been more specific.
Avoid hardcoding: It is good practice to not refer explicitly to whatever the user can change in a worksheet. Example: Sheets("New Appeals").Activate will work if the sheet name is New Appeals, but if the user changes the name, it will break. Use the sheet's ID instead. Similarly, in your code you assign string variables to hardcoded strings ("Date of Notification", etc). It is safer if you design an area in your sheet from where you can pull this data every time.
Dealing with lots of Cases: the best solution is to use a Dictionary object. It is more elegant, and the code is less cluttered. An example is here.
Copying and Pasting: Use the Range.Copy and Range.PasteSpecial Methods instead of the Selection ones. also, it is not always necessary to Activate a sheet in order to copy/paste there. The Range object can do useful stuff (searching, specialcells, etc.). Check it out.
Fully qualify Ranges: When you copy-paste data from different sheets/files, always use the full name of your Range/Cells objects, to avoid bugs. Relevant SO post here.
Dealing with Large Ranges: Passing data between Excel and VBA can be time-consuming when the numbers get bigger. A good practice is to pass the excel data to a Variant Array, do whatever processing and then dump them back to excel. This is a good read.
Use of With blocks: When you refer to an object's properties/methods multiple times, it enhances readability. Example here.
I hope this helps. This is by not means an exhaustive list, but it might be useful to start with. Happy coding!

Why does `PasteSpecial` method force the activecell to change?

I'm surprised there aren't really any great ways to multiply a Range by a constant value. Sure you can use a loop, but that seems pretty long-winded for something that seems so simple. You can also use the Evaluate statement, but the information page is so vague on what this actually does, so I would rather not. That leaves us with the PasteSpecial method, which for some reason forces the active cell to change. This is annoying, because if you want to keep the activecell default on the page you're pasting it onto, you now have to add 4 lines of code to achieve it.
Before:
Sub sq()
Range("A1").Copy
Sheets(2).Range("F1:F20").PasteSpecial xlPasteValues, xlPasteSpecialOperationMultiply
Application.CutCopyMode = False
End Sub
After:
Sub sq()
Sheets(1).Range("A1").Copy
Sheets(2).Activate
adr = ActiveCell.Address
Sheets(2).Range("F1:F20").PasteSpecial xlPasteValues, xlPasteSpecialOperationMultiply
Application.CutCopyMode = False
Sheets(2).Range(adr).Select
Sheets(1).Activate
End Sub
Is this the generally accepted method of simply multiplying a range by a value or am I unaware of something else?
If you are looking for a 'cool' method of applying a Paste Special, Multiply to a range of cells without using .Select, consider adding a UDF to your project.
Sub sq()
Dim rMultiplier As Double
With Worksheets(1)
rMultiplier = .Range("A1").Value2
End With
With Worksheets(2).Range("F1:F20")
.Cells = udfPasteSpecialMultiply(.Cells, rMultiplier)
End With
With Worksheets(2).Range("H2:K2")
.Cells = udfPasteSpecialMultiply(.Cells, rMultiplier)
End With
End Sub
Function udfPasteSpecialMultiply(rng As Range, dMult As Double)
Dim v As Long, w As Long, vVALs As Variant
vVALs = rng.Value2
For v = LBound(vVALs, 1) To UBound(vVALs, 1)
For w = LBound(vVALs, 2) To UBound(vVALs, 2)
vVALs(v, w) = vVALs(v, w) * dMult
Next w
Next v
udfPasteSpecialMultiply = vVALs
End Function
The User Defined Function returns an array of modified values that are stuffed back into the cells after performing the equivalent of a [Range.PasteSpecial method] with the xlPasteSpecialOperationMultiply operation. No change to the selection or active cell on either worksheet is made.
In all fairness, you really shouldn't be relying on .Select, .Activate, ActiveCell or Selection to reference cells. As your coding skills develop these cell reference problems are going to get worse; not better. See How to avoid using Select in Excel VBA macros for more methods on getting away from relying on select and activate to accomplish your goals.

Select Method of Worksheet Class Failed

I have this sub in Excel 2010 which is supposed to filter through all the cells in a sheet until it finds a match to Proj No, then paste a field from this row into another field.
When I try to run the sub, it gives me an error 1004: Select Method of Worksheet Class Failed. I've marked the line where this occurs. Any assistance would be greatly appreciated.
Option Explicit
Private Sub btnNext_Click()
Dim ProjNo As String
Dim Col As String
Dim Row As String
Dim cell As Range
Unload Dialog
formWait.Show
Sheets("Sheet7").Activate
ProjNo = Worksheets("Sheet1").Range("D6").Value
Col = Cells(Rows.Count, "A").End(xlUp).Row
For Each cell In Range("A2:A" & Col) 
If cell.Value = ProjNo Then
Row = Row & cell.Row
End If
Next cell
Workbooks("Form.xlsm").Sheets("Sheet7").Range("Row, 6").Copy Destination:=Sheets("Sheet1").Range("19, 5") ‘Error
Unload formWait
End Sub
I don't know what GWP is, but I think you want to use ProjNo there. The Range property doesn't accept an argument like that. Unless you have a named range of "Row,6" which you don't because it's not a legal name, you have to supply Range with a valid range reference, like A6 or D2:D12, for example.
Also, you can't concatenate rows and use them in a Range reference to get a larger range. You would have to copy each row inside the loop, union the ranges as you go, or better yet, filter on the value that you want and copy the visible rows.
Try this:
Private Sub btnNext_Click()
With ThisWorkbook.Worksheets("Sheet7")
'filter for the project id
.Range("A1", .Cells(.Rows.Count, 1).End(xlUp)).Resize(, 6).AutoFilter 1, "=" & .Range("D6").Value
'copy the visible rows
.Range("F2", .Cells(.Rows.Count, 6).End(xlUp)).SpecialCells(xlCellTypeVisible).Copy _
ThisWorkbook.Worksheets("Sheet1").Cells(19, 5)
'get rid of the filter
.AutoFilterMode = False
End With
End Sub
There are a few confusing items in your code above, so I wanted to place them long-form here. Let's get started:
Dim Col As String
Dim Row As String
It looks like your design expects these to be of type Long rather than type String. Even if these variables were meant to be strings, I would recommend adjusting their names -- when your fellow developer attempts to review your design, he or she is likely to see names like "Col" or "Row" and think "these are numbers". Easy fix:
Dim Col As Long, Row As Long
The next issue comes up here:
Col = Cells(Rows.Count, "A").End(xlUp).Row
The structure above is a common method for identifying the last ROW, not column. (It also appears that you have switched the "A" and number, which is another easy fix). While it is perfectly acceptable syntactically to name the variable for last row "Col", human users are likely to find this confusing. Identifying the last row (and the last col, which you use in the For Each loop), as explained in fantastic detail here, would be better handled like this:
Dim SheetSeven As Worksheet, SheetOne As Worksheet
Dim LastRow As Long, LastCol As Long
Set SheetSeven = ThisWorkbook.Worksheets("Sheet7")
Set SheetOne = ThisWorkbook.Worksheets("Sheet1")
With SheetSeven
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
LastCol = .Range("A" & .Columns.Count).End(xlToLeft).Column
End With
This should make your For Each loop look like this:
With SheetSeven
For Each cell in .Range("A2:A" & LastCol)
'... do you comparison and row incrementing here
Next cell
End With
Once you've identified your sheet as a variable, the Range.Copy action should be much easier as well:
With SheetSeven
.Range(.Cells(Row, 6)).Copy _
Destination:=SheetOne.Range(SheetOne.Cells(19, 5))
End With
Also one other thing you may wish to check is the status of Application.ScreenUpdating.
With the release of Office 2013 and later, a SDI (Single Document Interface) was introduced. If Application.ScreenUpdating is False and the workbook is not active, the implied call to Workbook.Activate will fail. Check the status of ScreenUpdating and set it to True if needed. You can set it back to False after the first Activate call for that workbook is made.
See this article:
https://support.microsoft.com/en-us/help/3083825/excel-workbook-is-not-activated-when-you-run-a-macro-that-calls-the-wo
In my case the error came as the sheet was hidden.
so I check if I am not working with the hidden sheet. Or you need to unhide the sheet before you try to select or activate sheet.
For Each sh In ThisWorkbook.Sheets
If Left(sh.Name, 8) <> "Template" Then
sh.Select
sh.Range("A1").Select
End If
Next

How to improve the speed of VBA macro code?

I do not have much experience with writing macros, and therefore need the help of this community for the following issue encountered:
My macro copies a range of values entered in a vertical range in one worksheet and then pastes the values horizontally (transpose) in another worksheet. It would in theory paste the values from the first sheet to first row of the second worksheet which does not have content. Since the first five rows have contents, it thus pastes the values to the sixth row.
The problem I have with the running of the macro is that I feel like it is too slow and I would therefore like it to run faster.
I have the same macro doing the same thing but that instead pastes the values to another worksheet to the first row, and it runs perfect.
My best guess is therefore that the second macro is running slow because it has to start pasting on the sixth row and there may be some contents on the first 5 rows that take a lot of time for the macro to go through (there a lot of cell references to other workbooks) to determine where the next row for pasting should be. That is my best guess though and since I hardly know anything about macros, I cannot say for sure what the problem is.
I hereby provide you with the code of my macro and sincerely hope that somebody can tell me what is making my macro slow and provide me with a solution as to how to make it run faster. I am thinking that a solution might potentially be that the macro should not consider the first five rows of data and start pasting immediately on row 6 for the first entry. Then on row 7 the next time, and etc. This might be a solution but I do not know how to write the code in a way that it would do that.
Thank you for taking time and helping me to find a solution, here is the code:
Sub Macro1()
Application.ScreenUpdating = False
Dim historyWks As Worksheet
Dim inputWks As Worksheet
Dim nextRow As Long
Dim oCol As Long
Dim myCopy As Range
Dim myTest As Range
Dim lRsp As Long
Set inputWks = wksPartsDataEntry
Set historyWks = Sheet11
'cells to copy from Input sheet - some contain formulas
Set myCopy = inputWks.Range("OrderEntry2")
With historyWks
nextRow = .Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0).Row
End With
With inputWks
Set myTest = myCopy.Offset(0, 2)
If Application.Count(myTest) > 0 Then
MsgBox "Please fill in all the cells!"
Exit Sub
End If
End With
With historyWks
With .Cells(nextRow, "A")
.Value = Now
.NumberFormat = "mm/dd/yyyy hh:mm:ss"
End With
.Cells(nextRow, "B").Value = Application.UserName
oCol = 3
myCopy.Copy
.Cells(nextRow, 3).PasteSpecial Paste:=xlPasteValues, Transpose:=True
Application.CutCopyMode = False
End With
'clear input cells that contain constants
With inputWks
On Error Resume Next
With myCopy.Cells.SpecialCells(xlCellTypeConstants)
.ClearContents
Application.GoTo .Cells(1) ', Scroll:=True
End With
On Error GoTo 0
End With
Application.ScreenUpdating = True
End Sub
Just reiterating what has already been said:
Option Explicit
Sub Macro1()
'turn off as much background processes as possible
With Excel.Application
.ScreenUpdating = False
.Calculation = Excel.xlCalculationManual
.EnableEvents = False
End With
Dim historyWks As Excel.Worksheet
Dim inputWks As Excel.Worksheet
Dim nextRow As Long
Dim oCol As Long
Dim myCopy As Excel.Range
Dim myTest As Excel.Range
Dim lRsp As Long
Set inputWks = wksPartsDataEntry
Set historyWks = Sheet11
'cells to copy from Input sheet - some contain formulas
Set myCopy = inputWks.Range("OrderEntry2")
With historyWks
nextRow = .Cells(.Rows.Count, 1).End(Excel.xlUp).Offset(1, 0).Row
End With
With inputWks
Set myTest = myCopy.Offset(0, 2)
If Excel.Application.Count(myTest) > 0 Then
MsgBox "Please fill in all the cells!"
GoTo QuickExit
End If
End With
With historyWks
With .Cells(nextRow, 1)
.Value = Now
.NumberFormat = "mm/dd/yyyy hh:mm:ss"
End With
.Cells(nextRow, 2).Value = Excel.Application.UserName
oCol = 3
myCopy.Copy
.Cells(nextRow, 3).PasteSpecial Paste:=Excel.xlPasteValues, Transpose:=True
Excel.Application.CutCopyMode = False
End With
'clear input cells that contain constants
With inputWks
On Error Resume Next
With myCopy.Cells.SpecialCells(Excel.xlCellTypeConstants)
.ClearContents
Excel.Application.Goto .Cells(1) ', Scroll:=True
End With
On Error GoTo 0
End With
Calculate
QuickExit
With Excel.Application
.ScreenUpdating = True
.Calculation = Excel.xlAutomatic
.EnableEvents = True
End With
End Sub
I'd step through the macro line-by-line to try to locate which line is slow.
Another alternative - although not sure if it'll speed things up - is to avoid the clipboard and lose the copy/paste so you'd apply a method like the following to move the data:
Option Explicit
Sub WithoutPastespecial()
'WORKING EXAMPLE
Dim firstRange As Range
Dim secondRange As Range
Set firstRange = ThisWorkbook.Worksheets("Cut Sheet").Range("S4:S2000")
With ThisWorkbook.Worksheets("Cutsheets")
Set secondRange = .Range("A" & .Rows.Count).End(Excel.xlUp).Offset(1)
End With
With firstRange
Set secondRange = secondRange.Resize(.Rows.Count, .Columns.Count)
End With
secondRange.Value = firstRange.Value
End Sub
Best way to improve performance based on my experience is to work on variables in code rather than accessing the spreadsheet every time you want to lookup a value.
Save any range you want to work with in a variable(variant) and then iterate through it as if it was the sheet.
dim maxRows as double
dim maxCols as integer.
dim data as variant
with someSheet
maxRows = .Cells(rows.count, 1).end(xlUp).row 'Max rows in sheet
maxCols = .Cells(1, columns.count).end(xlToLeft).column 'max columns in sheet
data = .Range(.Cells(1,1), .Cells(maxRows, maxCols)) 'copy range in a variable
end with
From here you can access the data variable as if it was the spreadsheet like - data(row, column) with MUCH MUCH faster read speed.
Please take a look at this article as well.
How to speed up calculation and improve performance...
By all means, Application.calculation= xlCalculationManual is usually the culprit. But we can notice that volatile Excel sheet functions can mostly kill your application on large scale of data processing and functional aspect.
Also, for your current code following post might not be directly relevant. I find it useful for tips on over-all Excel/VBA performance optimization.
75 Excel speeding up tips
PS: I don't have enough reputation to comment on your post. So added as an answer..
Just a few suggestions (would have posted as a comment but I guess I don't have the rep):
Try refering to cell addresses instead of named ranges (doubt this would be the cause but may be causing some hit to performance)
Do your workbook formulas contain links to other workbooks? Try testing the code on a file with broken links to see if it improves performance.
If neither of these are the issue, my guess is that if the formulas are overly complex, there is probably some processing overhead being added. Try the code on a file containing only values to see if there is any improved performance.
As suggested by a few others in the comments, you should definitely change Application.Calculation to xlCalculationManual and rememeber to set it back to xlcalculationAutomatic at the end. Also try setting Application.Screenupdating = False (and turning that back on again too). Also, bear in mind that .Copy is a very inefficient way to copy cell values - if you really just want the values, loop through the range setting .Value to the .Values in the old range. If you need all the formatting, you're probably stuck with .Copy.
When you turn off the calc/screen refresh flags, please remember to turn them back on in all circumstances (even when your program exits at a different point, or causes a runtime error). Otherwise all sorts of bad things will happen. :)
You can improve the speed by stopping calculation during changing cell value and after that you can enable it. please follow the link.
http://webtech-training.blogspot.in/2013/10/how-to-stop-heavy-formula-calculation.html
.Cells(nextRow, 3).PasteSpecial Paste:=xlPasteValues, Transpose:=True
Application.CutCopyMode = False
I wouldn't do that. Cut,copy & Paste operations are the costliest operations, in terms of processor utilization, in an operating system.
Instead, you could just assign the value from one cell / range to an another cell / range, as in
Cells(1,1) = Cells(1,2) or Range("A1") = Range("B1")
Hope you got my point..