I’ve been told that the use of .Select and .Workbooks.Activate is not really a good way to write Vba. The code below tends to work perfectly and there doesn’t seem to be any real issues. The Activeworkbook is not a problem because of Workbooks("FUA.XLSM").Activate. My question is then, what would a good alternative/approach?
I’m sorry if this is a waste of time or it’s a stupid question, but I have heard that using these methods are not a good way to do it in the long run. I am worried that this will not work or create problems in the future. It should be noted that without Workbooks("FUA.XLSM").Activate the code tends to create errors as it gets confused about which workbook it should select.
In short, my question is, how am I able to avoid using Select and .Activate as to mitigate potential for errors in the future?
Code is as follows..
Dim wb1 As Excel.Workbook
Dim wb2 As Excel.Workbook
Set wb2 = Workbooks.Open("C:\Users\Ha.csv")
Set wb1 = Workbooks("FUA")
Dim sht1 As Worksheet
Dim sht2 As Worksheet
Dim copyRange As Range
Set sht1 = wb1.Sheets("Sheet1")
Set sht2 = wb2.Sheets("Ha")
With wb1.Sheets("Sheet1")
Range("AA3").Select
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lastRow = .Cells.Find(What:="*", _
After:=.Range("AA3"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).row
Else
lastRow = 1
End If
End With
Workbooks("FUA.XLSM").Activate
Range("AA3").Select
sht1.ListObjects.Add(xlSrcRange, , xlYes).Name = _
"Table1"
Range("Table1[#All]").Select
sht1.ListObjects("Table1").Range.AutoFilter Field:=9, Criteria1:= _
">=-1000000000000", Operator:=xlAnd, Criteria2:="<=1000000000000000"
Application.DisplayAlerts = False
Selection.SpecialCells(xlCellTypeVisible).Copy
Application.DisplayAlerts = True
Set wb2 = Workbooks.Open("C:\Users\Ha.csv")
Application.DisplayAlerts = False
wb2.Sheets("Ha").Paste
wb2.SaveAs Filename:= _
"C:\Users\Ha.csv", FileFormat:= _
xlCSV, CreateBackup:=False
Workbooks("Ha.csv").Close
End Sub
Probably get down voted for saying this but If it ain't broke don't fix it. Your code works fine already, no real reason to change unless it doesn't work or errors popping up. Sounds like you've tested it already.
Your code is already optimised and faster though here is an alternative for the sake of avoiding Select. If you get error 1004 with select methods similar to like the last line sht2.Range("A:I").Copy Columns(last_col + 1).PasteSpecial in the code below it will continue anyway.
Sub test()
Dim wb1 As Excel.Workbook
Set wb1 = Workbooks("XXX.XLSM") ' from here, use wb1 to refer to fua.xlsm
Dim wb2 As Excel.Workbook ' ditto for wb2
Set wb2 = Workbooks.Open("C:\Users\Ha.csv")
Dim sht1 As Worksheet ' ditto for sht1
Set sht1 = wb1.Sheets("Sheet1")
Dim sht2 As Worksheet
Set sht2 = wb2.Sheets("Ha")
If Application.WorksheetFunction.CountA(sht1.Cells) <> 0 Then
LastRow = sht1.Cells.Find( _
What:="*", _
After:=sht1.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).row
Else
LastRow = 1
End If
sht1.ListObjects.Add(xlSrcRange, sht1.Range("A:I"), xlYes).Name = "Table1"
sht1.ListObjects("Table1").Range.AutoFilter _
Field:=9, _
Criteria1:=">=-1000000000000", _
Operator:=xlAnd, _
Criteria2:="<=1000000000000000"
Application.DisplayAlerts = False ' not sure if needed
sht1.Range("A:I").SpecialCells(xlCellTypeVisible).Copy
On Error Resume Next
sht2.Range("A:I").Copy Columns(last_col + 1).PasteSpecial
On Error Resume Next
Application.DisplayAlerts = True ' not sure if needed
wb2.Save ' already C:\Users\Ha.csv
wb2.Close
End Sub
Here is your code rewritten.
I think it has the same functionality.
Sub test()
Dim wb1 As Excel.Workbook
Set wb1 = Workbooks("FUA.XLSM") ' from here, use wb1 to refer to fua.xlsm
Dim wb2 As Excel.Workbook ' ditto for wb2
Set wb2 = Workbooks.Open("C:\Users\Ha.csv")
Dim sht1 As Worksheet ' ditto for sht1
Set sht1 = wb1.Sheets("Sheet1")
If Application.WorksheetFunction.CountA(sht1.Cells) <> 0 Then
LastRow = sht1.Cells.Find( _
What:="*", _
After:=sht1.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
LastRow = 1
End If
sht1.ListObjects.Add(xlSrcRange, sht1.Range("AA3"), xlYes).Name = "Table1"
sht1.ListObjects("Table1").Range.AutoFilter _
Field:=9, _
Criteria1:=">=-1000000000000", _
Operator:=xlAnd, _
Criteria2:="<=1000000000000000"
Application.DisplayAlerts = False ' not sure if needed
Range("Table1[#All]").SpecialCells(xlCellTypeVisible).Copy sht2.Cells
Application.DisplayAlerts = True ' not sure if needed
wb2.Save ' already C:\Users\Ha.csv
wb2.Close
End Sub
I wouldn't even bother with using VBA to get the data, use Power Query and import the data from the source file, perform the filter in the query and return the result to a table in the "FUA" workbook.
Then the query can be set to automatically refresh on the "FUA" workbook opening or in the query definition.
Related
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.
I keep getting error 438 Object doesn't support this property.
For: wb1.SpecialCells(xlCellTypeVisible).Copy
I am trying to filter a table and copy paste it into a new CSV. I have declared the workbook. I have also tried sht2.Specialcells as well as
wb1.Range("AA2:AI222").SpecialCells(xlCellTypeVisible).Copy
My full code is below:
' Filtered Table
Sub Auto_close13()
'
' Macro2 Macro
'
Dim wb1 As Excel.Workbook
Dim wb2 As Excel.Workbook
Set wb2 = Workbooks.Open("C:\Ha.csv")
Set wb1 = Workbooks.Open("C:\1zzThe Betting System.xlsm")
Dim sht1 As Worksheet
Dim sht2 As Worksheet
Dim copyRange As Range
Set sht1 = wb1.Sheets("Sheet1")
Set sht2 = wb2.Sheets("Ha")
With wb1.Sheets("Sheet1")
Range("AA2").Select
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lastRow = .Cells.Find(What:="*", _
After:=.Range("AA2"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lastRow = 1
End If
End With
''Workbooks("1zzThe Betting System.xlsm").Activate
''sht1.Activate
sht1.Range("AA2").Select
sht1.ListObjects.Add(xlSrcRange, , xlYes).Name = _
"Table1"
sht1.Range("Table1[#All]").Select
sht1.ListObjects("Table1").Range.AutoFilter Field:=9, Criteria1:= _
">=-1000000000000", Operator:=xlAnd, Criteria2:="<=1000000000000000"
''sht1.Activate
Application.DisplayAlerts = True
wb1.Range("AA2:AI222").SpecialCells(xlCellTypeVisible).Copy
Application.DisplayAlerts = True
Set wb2 = Workbooks.Open("C:\Ha.csv")
Application.DisplayAlerts = True
wb2.Sheets("Ha").Paste
wb2.SaveAs Filename:= _
"C:\Ha.csv", FileFormat:= _
xlCSV, CreateBackup:=False
Workbooks("Ha.csv").Close
''wb1.Close
End Sub
You already Set all the Worobook and Worksheet object in the begining of your code, so you can just use these objects. Like, Sht1 Sht2 etc...
You can use ListObject to set your Table.
Note: your error comes because you are missing the Worksheet object in your copy line:
wb1.Range("AA2:AI222").SpecialCells(xlCellTypeVisible).Copy
(as mentioned by #Jean-Pierre Oosthuizen commnet above)
See slightly modifed code below:
Dim LastRow As Long
Dim Tbl1 As ListObject
Set Sht1 = wb1.Sheets("Sheet1")
Set Sht2 = wb2.Sheets("Ha")
With Sht1
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
LastRow = .Cells.Find(What:="*", _
After:=.Range("AA2"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
LastRow = 1
End If
End With
Sht1.Range("AA2").Select
Set Tbl1 = Sht1.ListObjects.Add(xlSrcRange, , xlYes) ' <-- use ListObject to Set the Table
With Tbl1
.Name = "Table1"
.Range.AutoFilter Field:=9, Criteria1:= _
">=-1000000000000", Operator:=xlAnd, Criteria2:="<=1000000000000000"
End With
Application.DisplayAlerts = True
' Copy >> Paste in 1 line
Sht1.Range("AA2:AI222").SpecialCells(xlCellTypeVisible).Copy Destination:=Sht2.Range("AA2")
wb2.SaveAs Filename:="C:\Ha.csv", FileFormat:=xlCSV, CreateBackup:=False
wb2.Close SaveChanges:=False
I am trying to copy a series of worksheets from an Excel workbook and paste them into a new workbook. The issue that I am having is that when I copy worksheets into a new workbook the formulas still a reference to the old workbook in the formulas. I tried to get the name of the workbook and replace it with a null character but I believe my code is referencing the new workbook and not the old one. I tried a function as well as 'ThisWorkbook' as well as 'ActiveWorkbook' but none seem to be working.
Here is the function....
Function MyName() As String
MyName = ThisWorkbook.Name
End Function
Here is the full code....
Sub CopyToNewWorkbook()
Dim ws As Worksheet
Dim i As Integer
Dim wbCurrent As Workbook
Dim wbName As Variant
Dim wbNew As Workbook
'wbName = ActiveWorkbook.Name
'wbName = ThisWorkbook.Name
Set wbCurrent = ActiveWorkbook
Set wbNew = Workbooks.Add
For Each ws In wbCurrent.Sheets
Do While wbNew.Sheets.Count <= (wbCurrent.Sheets.Count - 3)
For i = 3 To wbCurrent.Sheets.Count
wbCurrent.Sheets(i).Copy after:=wbNew.Sheets(wbNew.Sheets.Count)
Next i
Loop
Next ws
wbNew.Activate
Sheets("Sheet1").Select
ActiveWindow.SelectedSheets.Delete
Cells.Replace What:=MyName, Replacement:="", LookAt:=xlPart, SearchOrder:=xlByRows, _
MatchCase:=False, SearchFormat:=False, ReplaceFormat:=False
End Sub
I was able to get the intended results with the following code.
Sub CopyToNewWorkbook()
Dim ws As Worksheet
Dim i As Integer
Dim wbCurrent As Workbook
Dim wbName As Variant
Dim wbNew As Workbook
Call MyName
wbName = MyName
Set wbCurrent = ActiveWorkbook
Set wbNew = Workbooks.Add
For Each ws In wbCurrent.Sheets
ws.Visible = xlSheetVisible
Do While wbNew.Sheets.Count <= (wbCurrent.Sheets.Count - 3)
For i = 3 To wbCurrent.Sheets.Count
wbCurrent.Sheets(i).Copy after:=wbNew.Sheets(wbNew.Sheets.Count)
Next i
Loop
Next ws
wbNew.Activate
Sheets("Sheet1").Select
ActiveWindow.SelectedSheets.Delete
Cells.Replace What:=MyName, Replacement:="", LookAt:=xlPart, SearchOrder:=xlByRows, _
MatchCase:=False, SearchFormat:=False, ReplaceFormat:=False
Cells.Replace What:="'[" & wbName & "]", Replacement:="'", LookAt:=xlPart, SearchOrder:=xlByRows, MatchCase:= _
False, SearchFormat:=False, ReplaceFormat:=False
End Sub
Could you not just use the BreakLinks method?
'Get all links
ExternalLinks = wbNew.LinkSources(Type:=xlLinkTypeExcelLinks)
'Break each link
For x = 1 To UBound(ExternalLinks)
wbNew.BreakLink Name:=ExternalLinks(x), Type:=xlLinkTypeExcelLinks
Next x
Hi currently i have a code that helps me to copy and paste information on a new found row from external workbook based on a matching condition such as "Singapore". The code will look through the sheet in the external workbook and search for all rows that have "Singapore" in the column and paste it to another workbook. But the problem i am facing right now is that the rows that is being copied and paste to is overlapping my column header instead of inserting on the last row of the sheet.
Below is the image that the information from the external workbook will be pasted to.
However when i run the code as below:
Sub UpdateNewUpcomingProj()
Dim wb1 As Workbook, wb2 As Workbook
Dim ws1 As Worksheet, ws2 As Worksheet
Dim copyFrom As Range
Dim lRow As Long '<~~ Not Integer. Might give you error in higher versions of excel
Dim strSearch As String
Set wb1 = Application.Workbooks.Open("U:\Active Master Project.xlsm")
Set ws1 = wb1.Worksheets("New Upcoming Projects")
strSearch = "Singapore"
With ws1
'~~> Remove any filters
.AutoFilterMode = False
'~~> I am assuming that the names are in Col A
'~~> if not then change A below to whatever column letter
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
With .Range("A1:A" & lRow)
.AutoFilter Field:=1, Criteria1:="=*" & strSearch & "*"
Set copyFrom = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
.AutoFilterMode = False
End With
'~~> Destination File
Set wb2 = ThisWorkbook
Set ws2 = wb2.Worksheets("New Upcoming Projects")
With ws2
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lRow = .Cells.Find(What:="*", _
After:=.Range("A2"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lRow = 2
End If
copyFrom.Copy
.Rows(lRow).PasteSpecial xlPasteAllExceptBorders, xlPasteSpecialOperationNone, False, False
.Rows.RemoveDuplicates Array(2), xlNo
End With
End Sub
It give this result:
It seems that the information is overlapping the column header instead of pasting it below the column header itself. I hope anyone could assist me in the codes to solve the problem of the rows being pasted on the column headers instead of on the empty rows. Any help would be appreciated. Thank you.
You might have to add the line
lRow = lRow + 1
after the section
lRow = .Cells.Find(What:="*", _
After:=.Range("A2"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
What's the most efficient way of selecting a column based on a variety of different possible header names? For example, the following gives me the column with header "school":
Rows("1:1").Select
Selection.Find(What:="School", _
After:=ActiveCell, _
LookIn:=xlFormulas, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False).Activate
ActiveCell.Select
Range(ActiveCell, ActiveCell.Offset(6536, 0)).Select
However, "school" could be "college" in another workbook, or "institution" in another. Should I just place the above code within an if-then-else statement and replace "school" with the other possibilities, or is there a more efficient way? And yes, this assumes that none of the possible header names co-exist within the same workbook.
Find is already very efficient. What's not efficient is all those Select's.
I suggest you wrap your Find Header logic into a Function, and refactor your code to avoid Select.
Private Function GetColumn(Header() As Variant, _
Optional NumRows As Long = 0, _
Optional ws As Worksheet = Nothing, _
Optional wb As Workbook = Nothing) As Range
Dim rng As Range, cl As Range
Dim i As Long
If wb Is Nothing Then
Set wb = ActiveWorkbook
End If
If ws Is Nothing Then
Set ws = wb.ActiveSheet
End If
Set rng = ws.UsedRange.Rows(1)
For i = LBound(Header) To UBound(Header)
Set cl = rng.Find(What:=Header(i), _
After:=rng.Cells(1, 1), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not cl Is Nothing Then
With ws
If NumRows = 0 Then
Set GetColumn = Range(cl, .Cells(.Rows.Count, cl.Column).End(xlUp))
Else
Set GetColumn = Range(cl, .Cells(NumRows, cl.Column))
End If
Exit Function
End With
End If
Next
Set GetColumn = Nothing
End Function
Call it like this
Dim rng As Range
Dim Headers() As Variant
Headers = Array("School", "Institution", "College")
' Active Workbook, Active Sheet
Set rng = GetColumn(Headers, 6536)
' All rows in specified column
' Specified sheet in Active workbook
Set rng = GetColumn(Headers, , Worksheets("SomeSheetName"))