VBA - PasteSpecial not working after using SpecialCells.Copy - vba

To sum up, I try to copy some filtered data from a workbook A to a workbook B keeping the formatting of the workbook B.
Here is the relevant part of my code:
With originSheet
.AutoFilterMode = False
With .Range("A7:AA" & lastRowOriginSheet)
.AutoFilter Field:=2, Criteria1:=projectNumber
.SpecialCells(xlCellTypeVisible).Copy
End With
End With
destinationSheet.Range("B4").PasteSpecial xlPasteValues
The paste special is not working and this is the formatting of the workbook A that is used.
Solved:
The problem was that you can't use PasteSpecial in a discontinuous range.
So I went with the solution of Siddharth Rout to go through all the areas of the filtered range:
With originSheet
.AutoFilterMode = False
With .Range("A7:AA" & lastRowOriginSheet)
.AutoFilter Field:=2, Criteria1:=projectNumber
Set filteredRange = .Offset(1, 0).SpecialCells(xlCellTypeVisible)
'~~> Loop through each area
For Each area In filteredRange.Areas
With destinationSheet
'~~> Find Next available row
lRow = .Range("B" & .Rows.Count).End(xlUp).Row + 1
area.Copy
destinationSheet.Range("B" & lRow).PasteSpecial xlPasteValues
End With
Next area
End With
End With

What #Jeeped has mentioned is very true that you cannot used Paste Special on a filtered range if they are Non Contiguous. However there is a way to achieve what you want :)
You have to loop through each area of the filtered range and then use Paste Special as shown below
Sub Sample()
Dim ws As Worksheet
Dim lastRowOriginSheet As Long
Dim filteredRange As Range, a As Range
Dim projectNumber As Long
'~~> I have set these for testing. Change as applicable
projectNumber = 1
Set ws = Sheet1
Set destinationSheet = Sheet2
lastRowOriginSheet = 16
With ws
.AutoFilterMode = False
With .Range("A7:AA" & lastRowOriginSheet)
.AutoFilter Field:=2, Criteria1:=projectNumber
Set filteredRange = .Offset(1, 0).SpecialCells(xlCellTypeVisible)
'~~> Loop through each area
For Each a In filteredRange.Areas
With destinationSheet
'~~> Find Next available row
lRow = .Range("B" & .Rows.Count).End(xlUp).Row + 1
a.Copy
destinationSheet.Range("B" & lRow).PasteSpecial xlPasteValues
End With
Next a
End With
End With
End Sub
In Action

PasteSpecial does not work on a discontiguous range. If you have one hidden row in among visible rows then you have a discontiguous range. However, due to the nature of a discontiguous range, a straight copy and paste will paste formats and the values from formulas; i.e. it cannot determine how to shift the cell ranges in formulas so it just pastes values.
With originSheet
.AutoFilterMode = False
With .Range("A7:AA" & lastRowOriginSheet)
.AutoFilter Field:=2, Criteria1:=projectNumber
'you should probably check to ensure you have visible cells before trying to copy them
.SpecialCells(xlCellTypeVisible).Copy destination:=destinationSheet.Range("B4")
End With
End With

Try this. Instead of doing PasteSpecial, since you just need values, you can set the ranges equal to eachother.
Dim copyRng As Range
With originSheet
.AutoFilterMode = False
With .Range("A7:AA" & lastRowOriginSheet)
.AutoFilter Field:=2, Criteria1:=projectNumber
Set copyRng = .SpecialCells(xlCellTypeVisible)
End With
End With
' destinationSheet.Range("B4").Value = copyRng.Value
With destinationSheet
.Range(.Cells(4, 2), .Cells(4 + copyRng.Rows.Count - 1, 2 + copyRng.Columns.Count - 1)).Value = copyRng.Value
End With
(this is assuming your worksheet and lastRow and projectNumber are all declared properly and working).
Edited because if you just do Range("B4").Value = Range("A1:Z100").Value, it's only going to put the first value in your copied range in the cell. You need to expand the destination range to be the size of the copy range.

Related

Only copy visible range in VBA?

I'm running into an issue where I'm unable to copy only visible cells to a new sheet. I'm able to get the lastrow, but I get #N/A on every cell except the first for each column. I want to just copy the visible cells. I'd also like to only put information on visible rows too, if possible?
Please see my code below:
Sub Importe()
lastRow = Worksheets("Sheet1").Cells(1, 1).SpecialCells(xlCellTypeVisible).End(xlDown).Row
Worksheets.Add
With ActiveSheet
Range("A1:A" & lastRow).Value2 = _
ActiveWorkbook.Worksheets("Sheet1").Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible).Value
Range("B1:B" & lastRow).Value2 = _
ActiveWorkbook.Worksheets("Sheet1").Range("E1:E" & lastRow).SpecialCells(xlCellTypeVisible).Value
End With
End Sub
Something like .Value2 = .Value doesn't work on special cells of type visible, because …
… e.g. if lastRow = 50 and there are hiddenRows = 10 then …
your source Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible)
has lastRow - hiddenRows = 40 rows
but your destination Range("A1:A" & lastRow).Value2
has lastRow = 50 rows.
On the first you subtract the visible rows, so they are different in size. Therefore .Value2 = .Value doesn't work, because you cannot fill 50 rows with only 40 source rows.
But what you can do is Copy and SpecialPaste
Option Explicit
Sub Importe()
Dim lastRow As Long
lastRow = Worksheets("Sheet1").Cells(1, 1).SpecialCells(xlCellTypeVisible).End(xlDown).Row
Worksheets.Add
With ActiveSheet
ActiveWorkbook.Worksheets("Sheet1").Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible).Copy
.Range("A1").PasteSpecial xlPasteValues
ActiveWorkbook.Worksheets("Sheet1").Range("E1:E" & lastRow).SpecialCells(xlCellTypeVisible).Copy
.Range("B1").PasteSpecial xlPasteValues
End With
End Sub
Nevertheless I recommend to avoid ActiveSheet or ActiveWorkbook if this is possible and reference a workbook eg by ThisWorkbook. My suggestion:
Option Explicit
Sub Importe()
Dim SourceWs As Worksheet
Set SourceWs = ThisWorkbook.Worksheets("Sheet1")
Dim DestinationWs As Worksheet
Set DestinationWs = ThisWorkbook.Worksheets.Add
Dim lastRow As Long
lastRow = SourceWs.Cells(1, 1).SpecialCells(xlCellTypeVisible).End(xlDown).Row
SourceWs.Range("H1:H" & lastRow).SpecialCells(xlCellTypeVisible).Copy
DestinationWs.Range("A1").PasteSpecial xlPasteValues
SourceWs.Range("E1:E" & lastRow).SpecialCells(xlCellTypeVisible).Copy
DestinationWs.Range("B1").PasteSpecial xlPasteValues
End Sub
To define whether a cell is visible or not, both its column and row should be visible. This means, that the .Hidden property of the column and the row should be set to False.
Here is some sample code of how to copy only the visible ranges between two worksheets.
Imagine that you have an input like this in Worksheets(1):
Then you manually hide column B and you want to get in Worksheets(2) every cell from the Range(A1:C4), without the ones in column B. Like this:
To do this, you should check each cell in the range, whether its column or row is visible or not.
A possible solution is this one:
Sub TestMe()
Dim myCell As Range
For Each myCell In Worksheets(1).Range("A1:C4")
If (Not Rows(myCell.Row).Hidden) And (Not Columns(myCell.Column).Hidden) Then
Dim newCell As Range
Set newCell = Worksheets(2).Cells(myCell.Row, myCell.Column)
newCell.Value2 = myCell.Value2
End If
Next myCell
End Sub
Just a general advise - whenever you use something like this Range("A1").Value2 = Range("A1").Value2 make sure that both are the same and not the left is Value2 and the right is .Value. It probably will not bring what you are expecting.
You cannot perform a direct value transfer without cycling though the areas of the SpecialCells(xlCellTypeVisible) collection.
Sometimes it is easier to copy everything and get rid of what you don't want.
Sub Importe()
Dim lr As Long
Worksheets("Sheet1").Copy after:=Worksheets("Sheet1")
With ActiveSheet
.Name = "xyz"
.Cells(1, 1).CurrentRegion = .Cells(1, 1).CurrentRegion.Value2
For lr = .Cells(.Rows.Count, "A").End(xlUp).Row To 1 Step -1
If .Cells(lr, "A").EntireRow.Hidden Then
.Cells(lr, "A").EntireRow.Delete
End If
Next lr
lr = .Cells(.Rows.Count, "A").End(xlUp).Row
.Cells(1, 1).CurrentRegion.Resize(lr, 1) = .Cells(1, 1).CurrentRegion.Resize(lr, 1).Offset(0, 7).Value2
.Cells(1, 1).CurrentRegion.Offset(0, 1).Resize(lr, 1) = .Cells(1, 1).CurrentRegion.Resize(lr, 1).Offset(0, 4).Value2
.Columns("C:XFD").EntireColumn.Delete
End With
End Sub
just to throw in an alternative version:
Sub Importe()
Dim sht1Rng As Range, sht1VisibleRng As Range
With Worksheets("Sheet1")
Set sht1Rng = .Range("A1", .Cells(.Rows.Count, 1).End(xlUp))
End With
Set sht1VisibleRng = sht1Rng.SpecialCells(xlCellTypeVisible)
With Worksheets.Add
.Range("A1").Resize(sht1Rng.Rows.Count).Value2 = sht1Rng.Offset(, 7).Value2
.Range("B1").Resize(sht1Rng.Rows.Count).Value2 = sht1Rng.Offset(, 4).Value2
.UsedRange.EntireRow.Hidden = True
.Range(sht1VisibleRng.Address(False, False)).EntireRow.Hidden = False
End With
End Sub
which may have the drawback of Address() maximum "capacity "

Copying the matched row in another sheet

I have two Sheets, sheet1 and sheet 2.
I am looking into column T of sheet1 and pasting the complete row if T contains 1 in sheet 2.
The code, works good, but it paste the result in sheet2 in the same row in sheet1.
This results in blanks, between the rows. Can anyone suggest, what i should Change with my code, so that i get them in sequence without any blank rows.
Also, how can I copy the Header in row 1 from sheet 1 to sheet2?
Sub Test()
For Each Cell In Sheets(1).Range("T:T")
If Cell.Value = "1" Then
matchRow = Cell.Row
Rows(matchRow & ":" & matchRow).Select
Selection.Copy
Sheets(2).Select
ActiveSheet.Rows(matchRow).Select
ActiveSheet.Paste
Sheets(1).Select
End If
Next
End Sub
There's no need to use Select and Selection to copy paste, it will only slows down your code's run-time.
Option Explicit
Sub Test()
Dim Cell As Range
Dim NextRow as Long
Application.ScreenUpdating = False
For Each Cell In Sheets(1).Range("T1:T" & Sheets(1).Cells(Sheets(1).Rows.Count, "T").End(xlUp).Row)
If Cell.Value = "1" Then
NextRow = Sheets(2).Cells(Sheets(2).Rows.Count, "T").End(xlUp).Row
Rows(Cell.Row).Copy Destination:=Sheets(2).Range("A" & NextRow + 1)
End If
Next
Application.ScreenUpdating = True
End Sub
Not For Points
Apologies, but I couldn't stop myself from posting an answer. It pains me when I see someone wanting to use an inferior way of doing something :(
I am not in favor of looping. It is very slow as compared to Autofilter.
If you STILL want to use looping then you can make it faster by not copying the rows in the loop but in the end in ONE GO...
Also if you do not like living dangerously then always fully qualify your object else you may end up copying the wrong row.
Option Explicit
Sub Sample()
Dim wsI As Worksheet, wsO As Worksheet
Dim lRow As Long, i As Long, r As Long
Dim copyRng As Range
Set wsI = Sheet1: Set wsO = Sheet2
wsO.Cells.Clear
'~~> first available row in sheet2
r = 2
With wsI
lRow = .Range("T" & .Rows.Count).End(xlUp).Row
'~~> Copy Headers
.Rows(1).Copy wsO.Rows(1)
For i = 1 To lRow
If .Range("T" & i).Value = 1 Then
If copyRng Is Nothing Then
Set copyRng = .Rows(i)
Else
Set copyRng = Union(copyRng, .Rows(i))
End If
End If
Next i
End With
If Not copyRng Is Nothing Then copyRng.Copy wsO.Rows(r)
End Sub
Screenshot

SpecialCells(xlCellTypeVisible)

I have 15 columns of data, with rows ranging from 400 - 1000, and I have applied filters, I am keen to only copy visible cells, for column D and J, onto a different sheet, but paste special values through transpose into range D6.
I have used this below method, but it is only copying two visible rows, and not every single row according to the code, like it has done for me in the past for other sheets I have run after amending it. The problem could be I am running three or four process in one macro.
I would be keen to know how I can amend this code so it copies column d and column j visible cells, excluding headers into a different sheet
So where do I stand with the code, it runs and applies the filters, but fails to copy all the rows for this particular part of the macro and secondly, I would be keen to know how to amend it so it only copies the Column D and J as the above excluding headers and only copies visible cells for to paste special values through transpose.
Set rngToCopy = .Offset(1, 0).SpecialCells(xlCellTypeVisible).Copy
Report.Range("D6").PasteSpecial xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=True
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
Dim rngToCopy As Range, rRange As Range
Set ws = Sheets("Sheet1")
With ws
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set rRange = .Range("A1:A" & lRow)
'~~> Remove any filters
.AutoFilterMode = False
With rRange 'Filter, offset(to exclude headers) and copy visible rows
.AutoFilter Field:=1, Criteria1:="<>"
Set rngToCopy = .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow
End With
'~~> Remove any filters
.AutoFilterMode = False
rngToCopy.Copy
'
'~~> Rest of the Code
'
End With
End Sub
I added thomas code to sub piece to see if the autofilter works and getting error 91
Sub Filter()
Dim Sheetx As Worksheet
Dim rngToCopy As Range, rRange As Range
With Sheetx
Set rRange = .Range("A1", .Range("A" & .Rows.Count).End(xlUp))
With rRange
.AutoFilter Field:=11, Criteria1:="30"
.AutoFilter Field:=4, Criteria1:="1"
.AutoFilter Field:=2, Criteria1:="=*1", _
Operator:=xlAnd
With .SpecialCells(xlCellTypeVisible)
Set rngToCopy = Union(.Offset(0, 3), .Offset(0, 9))
End With
rngToCopy.Copy
End With
End With
End Sub
We can use Union and Range.Offset to join the cells together define the range.
MSDN: Application.Union Method (Excel)
Returns the union of two or more ranges.
Sub Sample()
Dim lRow As Long
Dim rngToCopy As Range, rRange As Range
With Sheets("Sheet1")
With .Range("A1").CurrentRegion
.AutoFilter Field:=11, Criteria1:="=30"
.AutoFilter Field:=4, Criteria1:="=1"
.AutoFilter Field:=2, Criteria1:="=1", _
Operator:=xlAnd
On Error Resume Next
Set rngToCopy = .SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If rngToCopy Is Nothing Then
MsgBox "SpecialCells: No Data", vbInformation, "Action Cancelled"
Exit Sub
End If
Set rngToCopy = Intersect(rngToCopy, .Range("B:B,H:H"))
If rngToCopy Is Nothing Then
MsgBox "Intersect: No Data", vbInformation, "Action Cancelled"
Exit Sub
End If
End With
End With
rngToCopy.Copy
Sheets("Sheet2").Range("C6").PasteSpecial xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=True
End Sub

VBA Copy and Paste in another Sheet from AutoFilter outputting one row

I have an AutoFilter that once it is applied it always outputs one row. I want to copy this one row and paste it on another Sheet.
I have considered:
xlCellTypeAllValidation but it throws out an error
xlCellTypeSameValidation there are many validation criteria an AutoFilter
xlCellTypeLastCell but it gives the location of the last cell in the filtered row
How can i do this?
Here is an excerpt from my code:
With ThisWorkbook.Sheets(k).Range("A1:AZ1")
.Value = .Value
.AutoFilter field:=1, Criteria1:=Rev_1
.AutoFilter field:=11, Criteria1:=Beginnings(k)
.AutoFilter field:=12, Criteria1:=End_Instnts(k)
For zz = 13 To last_Field
.AutoFilter field:=zz, Criteria1:=""
Next zz
.SpecialCells(xlCellTypeLastCell).Select
.Range.Select
ThisWorkbook.Sheets(k).AutoFilterMode = False
End With
I'd recommend testing to ensure something actually matched the criteria before you copy - something like:
With ThisWorkbook.Sheets(k).Range("A1").CurrentRegion.Resize(, 52)
.Value = .Value
.AutoFilter field:=1, Criteria1:=Rev_1
.AutoFilter field:=11, Criteria1:=Beginnings(k)
.AutoFilter field:=12, Criteria1:=End_Instnts(k)
For zz = 13 To last_Field
.AutoFilter field:=zz, Criteria1:=""
Next zz
' make sure there are results matching filter
If .Columns(1).SpecialCells(xlCellTypeVisible).Count > 1 Then
' offset and resize to avoid headers then copy
.Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible).Copy Destination:=Sheets("other sheet").Range("A1")
End If
ThisWorkbook.Sheets(k).AutoFilterMode = False
End With
You can select all filtered region and then copy it, it will copy visible rows only anyway. Or combine it with .SpeciallCells(xlCellTypeVisible)
Smthng like (after End With) (assuming data starts from Row 2)
Range("A2:AZ1").Copy Destination:=PasteRange
One approach is to use Special Cells targeting visible cells only. One really quick and painless variant is to just use offset.
See the following:
Sub CopyFilterResult()
Dim WS1 As Worksheet, WS2 As Worksheet
With ThisWorkbook
Set WS1 = .Sheets("Sheet1")
Set WS2 = .Sheets("Sheet2")
End With
'Apply your filters here.
WS1.UsedRange.Offset(1, 0).Copy WS2.Range("A1")
End Sub
Screenshots:
Source (with filter):
Result:
Something to keep as a an alternative.
Let us know if this helps.
EDIT:
This code is as per exchange in comments. Read the comments and modify it to suit your needs.
Sub CopyAfterFilterMk2()
Dim WS1 As Worksheet, WS2 As Worksheet
Dim RngBeforeFilter As Range, RngAfterFilter As Range
Dim LCol As Long, LRow As Long
With ThisWorkbook
Set WS1 = .Sheets("Sheet1")
Set WS2 = .Sheets("Sheet2")
End With
With WS1
'Make sure no other filters are active.
.AutoFilterMode = False
'Get the correct boundaries.
LRow = .Range("A" & .Rows.Count).End(xlUp).Row
LCol = .Range("A1").End(xlToRight).Column
'Set the range to filter.
Set RngBeforeFilter = .Range(.Cells(1, 1), .Cells(LRow, LCol))
RngBeforeFilter.Rows(1).AutoFilter Field:=1, Criteria1:="o"
'Set the new range, but use visible cells only.
Set RngAfterFilter = .Range(.Cells(2, 1), .Cells(LRow, LCol)).SpecialCells(xlCellTypeVisible)
'Copy the visible cells from the new range.
RngAfterFilter.Copy WS2.Range("A1")
'Turn off the filter.
.AutoFilterMode = False
End With
End Sub
This code handles multiple rows post-filter as well.
Let us know if this helps.

Get Last Row From Filtered Range

How do you find the last row of data when the data in your worksheet is filtered? I have been playing around with Special Cells and Visible Cells but cannot find a solution. I think it must be some kind of variation on what I have below:
...
With ws
LR = .Range("A" & Rows.Count).End(xlUp).Row
.Range("A1:E" & LR).AutoFilter Field:=2, Criteria1:="=4"
LRfilt = .Range("A" & Rows.SpecialCells(xlCellTypeVisible).Count).End(xlUp).Row
Debug.Print LR
Debug.Print LRfilt
End With
...
File can be found here:
wikisend.com/download/443370/FindLRFilteredData.xls
Edit:
Realised after discussion with Siddharth I did not want the Last Row property I needed to find a count of the number of visible rows which led on to Sid's solution below...
After the filter, using the same formula for the lastrow will return the last filtered row:
...
With ws
LR = .Range("A" & Rows.Count).End(xlUp).Row
.Range("A1:E" & LR).AutoFilter Field:=2, Criteria1:="=4"
LRfilt = .Range("A" & Rows.Count).End(xlUp).Row
Debug.Print LR
Debug.Print LRfilt
End With
...
EDIT: Post Chat Followup
Option Explicit
Sub FilterTest()
Dim rRange As Range, fltrdRng As Range, aCell As Range, rngToCopy As Range
Dim ws As Worksheet
Dim LR As Long
'~~> Change this to the relevant sheet
For Each ws In ThisWorkbook.Worksheets
If Not ws.Name = "Sheet1" Then
With ws
'~~> Remove any filters
.AutoFilterMode = False
LR = .Range("A" & Rows.Count).End(xlUp).Row
'~~> Change this to the relevant range
Set rRange = .Range("A1:E" & LR)
With rRange
'~~> Some Filter. Change as applicable
.AutoFilter Field:=2, Criteria1:=">10"
'~~> Get the filtered range
Set fltrdRng = .SpecialCells(xlCellTypeVisible)
End With
For Each aCell In fltrdRng
If aCell.Column = 1 Then
If rngToCopy Is Nothing Then
Set rngToCopy = aCell
Else
Set rngToCopy = Union(rngToCopy, aCell)
End If
End If
Next
Debug.Print ws.Name
Debug.Print rngToCopy.Address
'rngToCopy.Copy
Set rngToCopy = Nothing
'~~> Remove any filters
.AutoFilterMode = False
End With
End If
Next
End Sub
Assuming your data is already filtered, you can try this:
Range("A1").Select
Dim FinalRowFiltered as Long
Dim FR as as String
FinalRowFiltered = Range("A" & Rows.Count).End(xlUp).Row
FR = "A" & CStr(FinalRowFiltered)
Range(FR).Select
After a lot of researching, came up with different options and I put some of them together which seems to be working fine for me (I made it work in a Table):
Hope you find it useful.
ActiveSheet.ListObjects("Table").Range.SpecialCells(xlCellTypeVisible).Select
b = Split(Selection.Address, "$")
iRes = UBound(b, 1)
If iRes = -1 Then
iRes = 0
End If
LastRow = Val(b(iRes))
This seems to work. When filters are on the normal .end(xlUp) gives the last row of a filtered range, but not the last row of the sheet. I suggest you use this technique to get the last row:
Sub GetLastRow
' Find last row regardless of filter
If Not (ActiveSheet.AutoFilterMode) Then ' see if filtering is on if already on don't turn it on
Rows(1).Select ' Select top row to filter on
Selection.AutoFilter ' Turn on filtering
End if
b = Split(ActiveSheet.AutoFilter.Range.Address, "$") ' Split the Address range into an array based on "$" as a delimiter. The address would yeild something like $A$1:$H$100
LastRow= Val(b(4)) ' The last value of the array will be "100" so find the value
End sub
This is simplest solution
...
With ws
.Range("A1:E1").AutoFilter Field:=2, Criteria1:="=4"
LRfilt=.Range("A1", .Range("A1").End(xlDown)).End(xlDown).Row
Debug.Print LRfilt
End With
...