Trying Set Values Instead of copying and pasting - vba

I have been able to modify most of my VBA procedures to set ranges to equal other ranges to avoid copy and paste. It has speed up my code incredibly. However, there is a few cases where I can't figure out how to not use copy and paste. Below is one example:
Dim Creation2 As Worksheet
Dim HoleOpener As Worksheet
Dim Dal As Range
Dim Lad As Range
Dim Pal As Range
Dim LastRow As Long
Dim ws As Worksheet
Dim fndList As Variant
Dim rplcList As Variant
Set HoleOpener = Worksheets("HoleOpener")
LastRow = HoleOpener.Range("C" & Rows.Count).End(xlUp).Row
On Error Resume Next
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Creation2" And ws.Name <> "BitInfoTable" And ws.Name <> "DailyBitInfoTable" And ws.Name <> "BitRunInfoTable" And ws.Name <> "HoleOpener" Then
Set Lad = ws.Cells.Find(What:="StartCopy", LookIn:= _
xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=True, SearchFormat:=False).Resize(21, 25).Copy
Sheets("HoleOpener").Cells(Rows.Count, "A").End(xlUp).Offset(1).PasteSpecial xlPasteValues
End If
Next
When I search I can't find any examples of doing something similar to this without copy/paste.

You will simply need to swap the ranges I listed with your ranges in FoundRange and PasteRange and adjust the Resize to fit your needs ((21, 25)). You have to use .value here as well.
Here is a generic example of how to set two ranges equal to each other. Since your range is greater than one, you will need to ensure each range is of equal size in terms of rows and columns spanned.
Dim FoundRange As Range, PasteRange As Range
Set FoundRange = Range("A1:B10") 'Swap this for your found value ("Lad" in your code)
Set PasteRange = Range("C1").Resize(10, 2) 'Swap this for your destination value
PasteRange.Value = FoundRange.Value
Using the same logic as above & your code, the result will look something like this:
Dim Lad As Range, PasteRange As Range
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Creation2" And ws.Name <> "BitInfoTable" And ws.Name <> "DailyBitInfoTable" And ws.Name <> "BitRunInfoTable" And ws.Name <> "HoleOpener" Then
Set Lad = ws.Cells.Find(What:="StartCopy", LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=True).Resize(21, 25)
Set PasteRange = Sheets("HoleOpener").Cells(Rows.Count, "A").End(xlUp).Offset(1).Resize(21, 25)
Destiation.Value = Lad.Value
End If
Next ws

Basically, you are finding a range that meets a specific criteria, copying that range and pasting it in a new range.
This is how I would solve it without using copy/paste:
Find range as you have done
Iterate all cells in range (by column or row)
Store value of each cell in a variable
Set value of destination cell to variable stored in Step 3
As you can guess, it will require a lot more code, so it really is a trade-off between spending time writing and maintaining more code or optimizing for performance

Related

VBA Run-Time Error 438

I have been working on a small macro but have run into an error.
The function of the macro is as follows: There is a list of stocks in an existing sheet. The macro goes into the folders and opens a spreadsheet where the recommendations are stored. It then goes back to the original sheet, takes each stock code and then goes into the recommendations sheet to see if there is a corresponding stock and what its recommendation is.
The code works fine, however I am now getting a VBA run-time error 438 when I am trying to get the macro to switch which workbook it needs to work on.
The error occurs on the lines application.wb2.activate and then lower down again with application.wb2.activate and application.wb.activate
When I replace wb and wb2 with the full directory, i.e. H:\A\AA\recommendations.xlsx and H:\A\AA\november 2017.xlsm, it works fine.
Would appreciate any help here! Thanks!
Option Explicit
Option Compare Text
Sub gsr()
Dim firstrow As Integer, lastrow As Integer, i As Integer
Dim gsr As Range, msr As Range
Dim stock, findstock As Range, col As Integer
Dim sPath As String, sFile As String
Dim sPath2 As String, sFile2 As String
Dim wb As Workbook, wb2 As Workbook
Dim xlrange As Range, xlcell As Range, xlsheet As Worksheet
Dim xllastrow As Integer
Dim foundlocationG As String, foundlocationM As String
With ActiveWorkbook
sPath2 = ActiveWorkbook.Path & "\"
sFile2 = sPath2 & ActiveWorkbook.Name
Set wb2 = ActiveWorkbook
End With
sPath = "H:\A\AA\"
sFile = sPath & "Recommendations.xlsx"
Set wb = Workbooks.Open(sFile)
Set xlsheet = Sheets("Sheet1")
xllastrow = xlsheet.Range("A1").End(xlDown).Row
Set xlrange = xlsheet.Range("A1:A" & xllastrow)
Application.wb2.Activate
With wb2.Sheets("Sheet1").Range("A:Z")
Set stock = .Find(what:="Stock", After:=.Cells(.Cells.Count), LookIn:=xlValues, Lookat:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
Set gsr = .Find(what:="GS", After:=.Cells(.Cells.Count), LookIn:=xlValues, Lookat:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
Set msr = .Find(what:="MS", After:=.Cells(.Cells.Count), LookIn:=xlValues, Lookat:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
firstrow = stock.Row + 1
lastrow = .Cells(.Rows.Count, stock.Column).End(xlUp).Row
lastrow = lastrow - 1
col = stock.Column
For i = firstrow To lastrow
For Each xlcell In xlrange
If xlcell.Value = Cells(i, col) Then
Application.wb.Activate
foundlocationG = Cells(xlcell.Row, 2)
foundlocationM = Cells(xlcell.Row, 3)
Application.wb2.Activate
Cells(i, gsr.Column) = foundlocationG
Cells(i, msr.Column) = foundlocationM
End If
Next xlcell
Next i
End With
End Sub
You seem to be confusing the Workbook.Activate and Application.Activate¹ methods.
Activate is a direct method of the Workbook object. If you have correctly assigned (e.g. Set) an object-level variable to the Workbook object, you should be able to call the Activate method directly.
Solution: drop the Application and simply Activate the open workbook from the assigned object variable.
wb2.Activate
...
wb.Activate
For all intents and purposes, activating the workbooks as you have done is not necessary and is not the more efficient code. See How to avoid using Select in Excel VBA for more information.
¹ Application.Activate is more commonly used within Word VBA projects.

Object Required Run-Time Error VBA when searching different workbooks

I am fairly new to VBA so a lot of my code is what I have researched on the internet and put together. A bit background to what I am trying to achieve: -
I have two works books which have an identical layout. One work book is my original where the VBA code is held and the other is a type of overlay document. I have a column with a codes in the Overlay and need to search the original work book same column for this code if its found then copy entire row from overlay into the original and deleting the row found in the original, if its not found in the original just to copy row across.
The line of code I am getting the run-time error on is: -
Set rngFound = Workbooks("OverLay").Worksheets("Overlay").Range("G:G").Find(What:=r.Value, LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
Below is an extract of the code I am using.
Dim sht1 As Worksheet 'Current active worksheet (original version)
Dim sht2 As Worksheet 'Worksheet in OverLay
Dim rngFound As Range
Set sht2 = Workbooks("Overlay").Worksheets("Overlay")
With Workbooks("Original").Worksheets("Formatted")
lastRow = .Range("G" & .Rows.Count).End(xlUp).Row
End With
With sht2
For Each Row In .Range("G:G")
Set rngFound = Workbooks("OverLay").Worksheets("Overlay").Range("G:G").Find(What:=r.Value, LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
If Not rngFound Is Nothing Then
rngFound.Copy
Workbooks("Original").Worksheets("Formatted").Range(rngFound).PasteSpecial
End If
Next
End With
I'll start by showing you what's wrong:
Dim sht1 As Worksheet '// <~~ This never gets used?
Dim sht2 As Worksheet 'Worksheet in OverLay
Dim rngFound As Range
Set sht2 = Workbooks("Overlay").Worksheets("Overlay")
With Workbooks("Original").Worksheets("Formatted")
lastRow = .Range("G" & .Rows.Count).End(xlUp).Row
End With
With sht2
For Each Row In .Range("G:G")
'// 'Row' in the above line will be treated as a variant as it hasn't been declared.
'// As such, it will most likely default to a Range object, which means you are
'// actually looping through each cell in that column. The lesson here is "be explicit"
'// and make sure the code is looking at exactly what you want it to look at.
Set rngFound = Workbooks("OverLay").Worksheets("Overlay").Range("G:G").Find(What:=r.Value, LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
'// You've already set this sheet to 'sht2' so just use that instead. Also, as
'// we know - 'r' has not been set and so causes an error.
If Not rngFound Is Nothing Then
rngFound.Copy
Workbooks("Original").Worksheets("Formatted").Range(rngFound).PasteSpecial
'// 'rngFound' is already a range object, no need to wrap it in a Range() method.
End If
Next
End With
This can be re-written as such:
Dim originalWS As Worksheet '// give your variables meaningful names!
Dim overlayWS As Worksheet
Dim rngSearchParam As Range
Dim rngFound As Range
Set originalWS = Workbooks("Original").Sheets("Formatted")
Set overlayWS = Workbooks("Overlay").Sheets("Overlay")
With overlayWS
For Each rngSearchParam In Intersect(.Range("G:G"), .UsedRange)
Set rngFound = .Range("G:G").Find(rngSearchParam.Value, LookIn:=xlValues, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
If Not rngFound Is Nothing Then
originalWS.Range(rngFound.Address).Value = rngFound.Value
End If
Next
End With
Although it seems like your searching a column, for a value defined by a cell in the same column - so not sure what the "end goal" is here. Hopefully it clarifies the issues you've been having though

Copying certain columns VBA

I have a working VBA macro which copies from one spreadsheet 'AverageEarnings' to another 'Sheet1', under the condition that Column AO has the word 'UNGRADED' in it. The macro copies the entirety of these conditional rows to Sheet1. I am looking to copy columns B and C ('AverageEarnings') to columns A and B ('Sheet1'). How do I amend this.
Sub UngradedToSHEET1()
' UngradedToSHEET1 Macro
'
Dim wb1 As Workbook, wb2 As Workbook
Dim ws1 As Worksheet, ws2 As Worksheet
Dim copyFrom As Range
Dim lRow As Long
Dim stringToFind As String
Set wb1 = ThisWorkbook
Set ws1 = wb1.Worksheets("AverageEarnings")
stringToFind = "UNGRADED"
With ws1
'Remove all filters from spreadsheet to prevent loss of information.
.AutoFilterMode = False
lRow = .Range("AO" & .Rows.Count).End(xlUp).Row 'Find a specific column.
With .Range("AO1:AO" & lRow) ' This is the row where GRADED or UNGRADED is specified.
.AutoFilter Field:=1, Criteria1:="=*" & stringToFind & "*" 'Filter specific information.
Set copyFrom = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
'Remove spreadsheet filters again.
.AutoFilterMode = False
End With
Set ws2 = wb1.Worksheets("Sheet1")
With ws2
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then ' Find a blank row after A1.
lRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lRow = 1
End If
copyFrom.Copy .Rows(lRow)
End With
End Sub
This line copies the entire row:
Set copyFrom =
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
You will need to change EntireRow to just copy the columns you want, probably something like:
Set copyFrom =
.Offset(1, 0).SpecialCells(xlCellTypeVisible).Range(.Cells(1,2),.Cells(1,3))
Hope this helps, I can't check this right now.

Apply advanced filter after autofilter

I want to do two successive filtering; the first on dates which I use auto-filter and on the produced result I want to do advance-filter (because I have OR in mind).
So what I did first was to set a range variable to the unfiltered range.
Set rng = Range(ws.Cells(1, 1), ws.Cells(rowNos, colNos))
Then using auto-filter I filter for given dates.
rng.AutoFilter Field:=1, Criteria1:=">" & lDate
Since now some rows will be hidden, and I want to apply advanced filter, I made use of specialcells
rng.SpecialCells(xlCellTypeVisible).AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=crt, CopyToRange:=thisWB.Worksheets("Sheet3").Range("A1"), _
Unique:=False
However I got an error in the last step "the command requires at least two rowa of data . . " I made sure that there were at least 100 rows which fit the criteria meaning that the error is not because of a lack of rows.
Please help me get the problem. Also if there's another way I can accomplish the task I'll be happy to change my codes. What I'm trying to do is for specific dates filter a table and then filter again for values on two columns (as is normally done with advanced filters).
It seems like .AdvancedFilter does not work on non-continuous ranges. The code below is a bit kludge-y, but worked for a little example I pulled together where I want to return observations that are > April 1st, 2014 where Foo = Yes and Bar = 7. My data sheet contains exactly one row that matches all those criteria.
Option Explicit
Sub FilterTwice()
Dim DataSheet As Worksheet, TargetSheet As Worksheet, _
ControlSheet As Worksheet, TempSheet As Worksheet
Dim DataRng As Range, ControlRng As Range, _
TempRng As Range
Dim lDate As Date
Dim LastRow As Long, LastCol As Long
'assign sheets for easy reference
Set DataSheet = ThisWorkbook.Worksheets("Sheet1")
Set ControlSheet = ThisWorkbook.Worksheets("Sheet2")
Set TargetSheet = ThisWorkbook.Worksheets("Sheet3")
'clear any previously-set filters
Call ClearAllFilters(DataSheet)
'assign data range
LastRow = DataSheet.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
LastCol = DataSheet.Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Set DataRng = Range(DataSheet.Cells(1, 1), DataSheet.Cells(LastRow, LastCol))
'assign a control (or critieria) range for the advanced filter
Set ControlRng = Range(ControlSheet.Cells(1, 1), ControlSheet.Cells(2, 2))
'apply date filter
lDate = "4/1/2014"
With DataRng
.AutoFilter Field:=1, Criteria1:=">" & lDate
End With
'add a temporary sheet and copy the visible cells to create a continuous range
Set TempSheet = Worksheets.Add
DataRng.SpecialCells(xlCellTypeVisible).Copy
TempSheet.Range("A1").PasteSpecial Paste:=xlPasteAll
'assign temp range
LastRow = TempSheet.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
LastCol = TempSheet.Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Set TempRng = Range(TempSheet.Cells(1, 1), TempSheet.Cells(LastRow, LastCol))
'apply advanced filter to temp range and get obs where foo = yes and bar = 7
With TempRng
.AdvancedFilter Action:=xlFilterCopy, CriteriaRange:=ControlRng, _
CopyToRange:=TargetSheet.Range("A1"), Unique:=False
End With
'remove the temp sheet and clear filters on the data sheet
Application.DisplayAlerts = False
TempSheet.Delete
Application.DisplayAlerts = True
DataSheet.AutoFilterMode = False
End Sub
Sub ClearAllFilters(cafSheet As Worksheet)
With cafSheet
.AutoFilterMode = False
If .FilterMode = True Then
.ShowAllData
End If
End With
End Sub

Excel 2013 - How to copy data from one sheet to another based on formatting

I have an Excel sheet with values, and I want to automatically copy only the values that are highlighted (formatted with background color) to another sheet.
I don't mind using a macro, or if possible a function in the second sheet cells.
I've tried to put a few suggestions together and created a function to return the cell color and this following macro to filter by the color value:
Sub Sample()
Dim ws As Worksheet
Dim strSearch As String
Dim lRow As Long
Set ws = Sheets("Sheet1")
With ws
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Remove any filters
.AutoFilterMode = False
'~~> Filter, offset(to exclude headers) and set the source location
With .Range("J2:J" & lRow)
.AutoFilter Field:=1, Criteria1:="6"
Set copyFrom = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
'~~> Remove any filters
.AutoFilterMode = False
End With
'~~> Destination Sheet
Set ws2 = Sheets("Sheet2")
With ws2
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lRow = 1
End If
copyFrom.Copy .Rows(lRow)
End With
End Sub
Function InteriorColor(CellColor As Range)
InteriorColor = CellColor.Interior.ColorIndex
End Function
But, I need to copy from several sheets to a single sheet, and the macro states that the source sheet is constant... I wouldn't want to create a macro for each separate sheet.
In addition, each time I will run the macro, it will copy the highlighted lines which were already copied before thus creating duplicates in the destination sheet.
I've looked into many SO posts regarding this issue but couldn't find anything that suits my needs.
Can anyone suggest how to change this code or have a new optimized one?
Thank you in advance!
First off, I think this should be separated into two problems:
Import from different worksheets
If you want to import from different sheets, you should write your function to accept a sheet as a parameter.
Sub Sample(ws As Excel.Worksheet)
Then you need to remove both the lines Dim ws As Worksheet and Set ws = Sheets("Sheet1"), or you will get an error or unexpected behavior.
Then you need another function that loops through all the worksheets and calls your Sample(Worksheet) procedure.
Sub SampleCaller()
dim e, ws as Excel.Worksheet
For Each e in ThisWorkbook.Sheets
Set ws = e
Call Sample(ws)
Next e
Set ws = Nothing
End Sub
Find already existing rows in destination worksheet
You are already using the Range.Find method (Link).
You can use it to find already existing elements in your destination worksheet.
Before Calling copyFrom.Copy .Rows(lRow) you should use Range.Find to determine whether you need to copy this value.
Maybe you should create another topic for this second issue, or use the search function.