Hiding worksheet based on a list - vba

Hey have a quick question I am writing a code that hides a list of worksheets based on whether the field next to the worksheet name is yes or no. Therefore, I have list of 29 worksheets, I want my code to look at the field next to that name, and if its yes it while show it and if it is no It will hide it.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim ws As Worksheet
For Each ws In Worksheets
If ws.Name = Worksheets("Settings").Range("B4:B32") _
And Worksheets("Setting").Range("C4:C32") = "Yes" Then
ws.Visible = True
End If
If ws.Name = Worksheets("Settings").Range("B4:B32") _
And Worksheets("Setting").Range("C4:C32") = "No" Then
ws.Visible = True
End If
Next ws
End Sub
I run this and keep getting a mismatch error i am new to programming so i don't think i am calling stuff correctly

I believe the following code should suit your needs:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim wsSettings As Worksheet
Dim wsHideShow As Worksheet
Dim rngSheets As Range
Dim xlCell As Range
Dim sheetName As String
Set wsSettings = ThisWorkbook.Worksheets("Settings")
Set rngSheets = wsSettings.Range("B4:B32")
For Each xlCell In rngSheets
sheetName = xlCell.Value
If sheetName <> "" Then
Set wsHideShow = ThisWorkbook.Worksheets(sheetName)
If xlCell.Offset(0, 1).Value = "yes" Then
wsHideShow.Visible = False
Else
wsHideShow.Visible = True
End If
End If
Next xlCell
End Sub
Instead of looping through your worksheets, loop through your list and hide/show the worksheets accordingly.

This code runs in a standard module. It assumes that the master is Settings rather than Setting
It loops over the table as well as the sheets:
Sub DisplayOrHideSheets()
Dim sh As Worksheet
For Each ws In Sheets
v = ws.Name
For Each r In Worksheets("Settings").Range("B4:B32")
If r.Value = v Then
If r.Offset(0, 1) = "Yes" Then
ws.Visible = True
Else
ws.Visible = False
End If
End If
Next r
Next ws
End Sub

Being slow in typing... but... my suggestion would be:
Private Sub WorkSheet_Change(ByVal Target as range)
If (Target.Row >= 4 And Target.Row <= 32 And Target.Column = 2) Then
Dim i as Integer
For i=0 To 28 Step 1
If Range("B" & 2 + i).Value = "YES" Then
ThisWorkBook.Worksheets(Range("A" & 2 + i).Value).Visible = True
Else
ThisWorkBook.Worksheets(Range("A" & 2 + i).Value).Visible = False
End If
Next i
End If
End Sub
This is fireing only when the values in the range B4:B32 on the sheet are changed...
Hope this helps...

Related

Add Union to increase efficiency in VBA code

I have code within a workbook that works exactly how I want it to, but I am looking for ways to increase its efficiency/speed. My thought would be to add a union for all blank rows and hide that range all at once. Can this be done?
Sub HideRws()
Dim Rng As Range, Cl As Range
With Sheet3
For Each Cl In .Range("A11:A60")
Cl.EntireRow.Hidden = Cl.Value = ""
Next Cl
For Each Rng In .Range("A71:A120, A131:A180, A190:A239").Areas
If Rng(1) = "" Then
Rng.Offset(-6).Resize(58).EntireRow.Hidden = True
Else
For Each Cl In Rng
Cl.EntireRow.Hidden = Cl.Value = ""
Next Cl
End If
Next Rng
End With
End Sub
I think this does the same thing:
Sub HideRows()
With Sheet3
.Range("A11:A60").SpecialCells(xlCellTypeBlanks).EntireRow.Hidden = True
For Each Rng In .Range("A71:A120, A131:A180, A190:A239").Areas
If Rng(1) = "" Then
Rng.Offset(-6).Resize(58).EntireRow.Hidden = True
Else
Rng.SpecialCells(xlCellTypeBlanks).EntireRow.Hidden = True
End If
Next Rng
End With
End Sub

Hide rows if name is Sam and Ticket name Accela in Excel using VBA

Imagine we have three columns in Excel (No. Name Ticket) and we need to hide from the spreadsheet the user name Sam and ticket name ACCELA. I created the following code but it does not work.
Sub Hide_Rows()
Dim c As Range
For Each c In ActiveSheet.Range("A2:C37")
If c.Value = "Sam" Then
If c.Value = "ACCELA" Then
c.EntireRow.Hidden = False
End If
End If
Next c
End Sub
I also tried the following and did not work:
Sub Hide_Rows_Toggle()
Dim c As Range
For Each c In Range("A2:C37").Cells
If c.Value = "Sam" And c.Value = "ACCELA" Then
'The following line changes the hidden property to
'the opposite of it's current setting for the row.
c.EntireRow.Hidden = Not c.EntireRow.Hidden
End If
Next c
End Sub
Any help please.
Try this.
Sub Hide_Rows()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ThisWorkbook
Set ws = wb.Worksheets("Sheet1") ' change
Dim unionRange As Range
Dim c As Range
ws.Range("B2:B37").EntireRow.Hidden = False
For Each c In ws.Range("B2:B37")
If c = "Sam" And c.Offset(, 1) = "ACCELA" Then
If Not unionRange Is Nothing Then
Set unionRange = Union(unionRange, c)
Else
Set unionRange = c
End If
End If
Next c
If Not unionRange Is Nothing Then
unionRange.EntireRow.Hidden = True
End If
End Sub
you could use AutoFilter
Sub Hide_Rows()
Dim rngToHide As Range
With Worksheets("mySheetName").Range("B1:C37") ' change "mySheetName" to yur actual sheet name
.EntireRow.Hidden = False
.AutoFilter Field:=1, Criteria1:="Sam"
.AutoFilter Field:=2, Criteria1:="ACCELA"
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then Set rngToHide = .Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible)
.Parent.AutoFilterMode = False
End With
If Not rngToHide Is Nothing Then rngToHide.EntireRow.Hidden = True
End Sub

Excel VBA Large Table, Add Comments Vlookup, After Hitting Command Button

I have a large table and the information I'm wanting to add comments to falls within Range(D11:CY148). I have two tabs - "Finish Matrix" (main) and "list" (hidden - has 2 columns).
I have two issues.
First issue - Code works to a degree, after I type my values within a cell it automatically adds comments based off info in another sheet. The problem is there is too many cells to be manually typing into and if I copy and paste the code doesn't run. I created a CommandButton and wanted it to refresh the entire table with comments depending if the cells had the values that fall within "list". I tried to create a call out to Worksheet_Change but to no avail. (I'm a beginner so it'll help if you explain)
Second issue - I'm assuming it'll get fixed with whatever suggestion that works. Occasionally after typing into a cell I would get an error. Can't remember the error name but it is one of the common ones, atm the error isn't popping up but surely it'll come back since I didn't do anything different to the code.
Private Sub Worksheet_Change(ByVal Target As Range)
If Intersect(Target, Columns("A:CX")) Is Nothing Then _
If Intersect(Target, Columns("CY")) Is Nothing Then Exit Sub
Dim lRow As Integer
lRow = Sheets("list").Range("A1").End(xlDown).Row
If Target.Value = vbNullString Then Target.ClearComments
For Each cell In Sheets("list").Range("A1:A" & lRow)
If cell.Value = Target.Value Then
Target.AddComment
Target.Comment.Text Text:=cell.Offset(0, 1).Value
End If
Next cell
End Sub
Thanks for any and all help!
You are basically missing the For Each Cell in Target part...
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim wsMain As Worksheet, wsList As Worksheet
Dim cell As Range
Dim vCommentList As Variant
Dim i As Long, lLastRow As Long
Dim sValue As String
On Error GoTo ErrHandler
Application.ScreenUpdating = False
Application.EnableEvents = False
Set wsMain = Target.Parent
Set Target = Intersect(Target, wsMain.Range("D11:CY148"))
If Target Is Nothing Then Exit Sub
Set wsList = wsMain.Parent.Sheets("list")
lLastRow = LastRow(1, wsList)
' Read Comment List into Variant (for speed)
vCommentList = wsList.Range("A1:B" & lLastRow)
Target.ClearComments
' This...For each Cell in Target...is what you were missing.
For Each cell In Target
sValue = cell
For i = 1 To UBound(vCommentList)
If sValue = vCommentList(i, 1) Then
AddComment cell, CStr(vCommentList(i, 2))
Exit For
End If
Next
Next
ErrHandler:
If Err.Number <> 0 Then Debug.Print Err.Description
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Proper way to find last row ...
Public Function LastRow(Optional Col As Integer = 1, Optional Sheet As Excel.Worksheet) As Long
If Sheet Is Nothing Then Set Sheet = Application.ActiveSheet
LastRow = Sheet.Cells(Sheet.Rows.Count, Col).End(xlUp).Row
End Function
Add Comment Sub the allows appending is needed...
Public Sub AddComment(Target As Range, Text As String)
If Target.Count = 1 Then
If Target.Comment Is Nothing Then
Target.AddComment Text
Else
Target.Comment.Text Target.Comment.Text & vbLf & Text
End If
End If
End Sub
Untested, but this will take all the values in Range(D11:CY148) and add a comment based on a lookup from Sheet "list".
Sub testy()
Dim arr As Variant, element As Variant
Dim i As Long, j As Long, listItems As Long, rwLast As Long, clLast As Long
Dim comm As String
Dim rng As Range, cell As Range
listItems = Sheets("list").Range("A1").End(xlDown).Row
rwLast = Cells.SpecialCells(xlCellTypeLastCell).Row ' Adjust to fit your needs
clLast = Cells.SpecialCells(xlCellTypeLastCell).Column 'Idem
Set rng = Sheets("list").Range("A1:A" & listItems)
arr = Range("D11:CY148").Value
With Worksheets("Finish Matrix")
For i = 1 To rwLast - 10 'Adjust to make it more general, this is pretty rough
For j = 1 To clLast - 3 'Idem
If i = 3 Then
End If
comm = ""
For Each cell In rng
If arr(i, j) = cell.Value Then
comm = comm & Chr(13) & cell.Offset(0, 1).Value
End If
Next cell
If Not (comm = "") Then
.Cells(10, 3).Offset(i, j).ClearComments
.Cells(10, 3).Offset(i, j).AddComment
.Cells(10, 3).Offset(i, j).Comment.Text Text:=comm
End If
Next j
Next i
End With
End Sub

VBA - Delete Every Nth Row On Each Sheet Of Workbook (100k+ values per sheet)

I have a workbook with 10+ sheets, with hundreds of thousands of values in each (125k sheet1, 240k sheet 2, 400k sheet 3, etc.) I am trimming down the sheets by keeping every thousandth or so point in each sheet.
I have been unable to get the code to finish trimming data on the first sheet. The code has run for well over an hour without finishing the first sheet. I've tried with smaller data sets as well (~1000 points in 5 sheets), but the macro only successfully trims points on the first sheet. The other sheets are not modified
Below is the code I'm using to delete an interval of rows; it is the most customizable way to delete rows I could find (which is exactly what i'm looking for: customization/simplicity
lastRow = Application.ActiveSheet.UsedRange.Rows.Count
For i = 2 To lastRow Step 1 'Interval of rows to delete
Range(Rows(i), Rows(i + 997)).Delete Shift:=xlUp
Next i
The code for this specific task is inserted into a modified version of a codes found in this question *credit to those who originally wrote them
Question: Excel VBA Performance - 1 million rows - Delete rows containing a value, in less than 1 min
Here's Helper Functions paul bica used in his code
Public Sub FastWB(Optional ByVal opt As Boolean = True)
With Application
.Calculation = IIf(opt, xlCalculationManual, xlCalculationAutomatic)
.DisplayAlerts = Not opt
.DisplayStatusBar = Not opt
.EnableAnimations = Not opt
.EnableEvents = Not opt
.ScreenUpdating = Not opt
End With
FastWS , opt
End Sub
Public Sub FastWS(Optional ByVal ws As Worksheet = Nothing, _
Optional ByVal opt As Boolean = True)
If ws Is Nothing Then
For Each ws In Application.ActiveWorkbook.Sheets
EnableWS ws, opt
Next
Else
EnableWS ws, opt
End If
End Sub
Private Sub EnableWS(ByVal ws As Worksheet, ByVal opt As Boolean)
With ws
.DisplayPageBreaks = False
.EnableCalculation = Not opt
.EnableFormatConditionsCalculation = Not opt
.EnablePivotTable = Not opt
End With
End Sub
A nifty little code for generating a test set by marko2049:
Sub DevelopTest()
Dim index As Long
FastWB True
ActiveSheet.UsedRange.Clear
For index = 1 To 1000000 '1 million test
ActiveSheet.Cells(index, 1).Value = index
If (index Mod 10) = 0 Then
ActiveSheet.Cells(index, 2).Value = "Test String"
Else
ActiveSheet.Cells(index, 2).Value = "Blah Blah Blah"
End If
Next index
Application.StatusBar = ""
FastWB False
End Sub
After generating a test set and copying it to several sheets, I ran a modified version of the code below
The main body of the code was made by user marko5049
Sub DeleteRowFast()
Dim curWorksheet As Worksheet 'Current worksheet vairable
Dim rangeSelection As Range 'Selected range
Dim startBadVals As Long 'Start of the unwanted values
Dim endBadVals As Long 'End of the unwanted values
Dim strtTime As Double 'Timer variable
Dim lastRow As Long 'Last Row variable
Dim lastColumn As Long 'Last column variable
Dim indexCell As Range 'Index range start
Dim sortRange As Range 'The range which the sort is applied to
Dim currRow As Range 'Current Row index for the for loop
Dim cell As Range 'Current cell for use in the for loop
On Error GoTo Err
Set rangeSelection = Application.InputBox("Select the (N=) range to be checked", "Get Range", Type:=8) 'Get the desired range from the user
Err.Clear
M1 = MsgBox("This is recommended for large files (50,000 or more entries)", vbYesNo, "Enable Fast Workbook?") 'Prompt the user with an option to enable Fast Workbook, roughly 150% performace gains... Recommended for incredibly large files
Select Case M1
Case vbYes
FastWB True 'Enable fast workbook
Case vbNo
FastWB False 'Disable fast workbook
End Select
strtTime = Timer 'Begin the timer
Set curWorksheet = ActiveSheet
lastRow = CLng(rangeSelection.SpecialCells(xlCellTypeLastCell).Row)
lastColumn = curWorksheet.Cells(1, 16384).End(xlToLeft).Column
Set indexCell = curWorksheet.Cells(1, 1)
On Error Resume Next
If rangeSelection.Rows.Count > 1 Then 'Check if there is anything to do
lastVisRow = rangeSelection.Rows.Count
Set sortRange = curWorksheet.Range(indexCell, curWorksheet.Cells(curWorksheet.Rows(lastRow).Row, 16384).End(xlToLeft)) 'Set the sort range
sortRange.Sort Key1:=rangeSelection.Cells(1, 1), Order1:=xlAscending, Header:=xlNo 'Sort by values, lowest to highest
startBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, MatchCase:=False).Row
endBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, SearchDirection:=xlPrevious, MatchCase:=False).Row
curWorksheet.Range(curWorksheet.Rows(startBadVals), curWorksheet.Rows(endBadVals)).EntireRow.Delete 'Delete uneeded rows, deleteing in continuous range blocks is quick than seperated or individual deletions.
sortRange.Sort Key1:=indexCell, Order1:=xlAscending, Header:=xlNo 'Sort by index instead of values, lowest to highest
End If
Application.StatusBar = "" 'Reset the status bar
FastWB False 'Disable fast workbook
MsgBox CStr(Round(Timer - strtTime, 2)) & "s" 'Display duration of task
Err:
Exit Sub
End Sub
I've modified the above code as follows
Sub DeleteRowFastMod()
Dim lastRow As Long
Dim i As Long
Dim ws As Worksheet
Dim wb As Workbook
Set wb = Application.ActiveWorkbook
On Error GoTo Err
'Get the desired range from the user
Err.Clear
FastWB True 'Enable fast workbook
strtTime = Timer 'Begin the timer
On Error Resume Next
For Each ws In wb.Worksheets(1) 'Loop through sheets in workbook
ws.Activate
lastRow = Application.ActiveSheet.UsedRange.Rows.Count
If lastRow > 1 Then 'Check if there is anything to do
For i = 2 To lastRow Step 1 'Interval of rows to delete
Range(Rows(i), Rows(i + 997)).Delete Shift:=xlUp
Next i
End If
Next
Application.StatusBar = "" 'Reset the status bar
FastWB False 'Disable fast workbook
MsgBox CStr(Round(Timer - strtTime, 2)) & "s" 'Display duration of task
Err:
Exit Sub
End Sub
I am not sure how to further modify this code to run on each sheet in the workbook in a timely manner.
Thanks in advance for any guidance
You could use the same method as in the link
Excel VBA Performance - 1 million rows - Delete rows containing a value, in less than 1 min
The code bellow (Module 2) sets up test data - 30 million formulas (3 full columns) in 10 Worksheets
The sub in Module 1 loops through all sheets and
hides 1K rows sets
copies visible rows to a new sheet
deletes the initial sheet
Module 1 - Main Sub
Option Explicit
Public Sub TrimLargeData() 'Time: 12.531 sec
Const TRIM_SZ = 1000
Dim t As Double, wb As Workbook, ws As Worksheet
Dim lr As Long, r As Long, newWs As Worksheet, done As Collection
t = Timer: Set wb = ThisWorkbook
FastWB True
Set done = New Collection
For Each ws In wb.Worksheets
done.Add ws
Next
For Each ws In done
lr = ws.UsedRange.Rows.Count
For r = 1 To lr Step TRIM_SZ
If r >= lr - (TRIM_SZ + 1) Then
ws.Range(ws.Cells(r + 1, 1), ws.Cells(lr - 1, 1)).EntireRow.Hidden = True
Exit For
End If
ws.Range(ws.Cells(r + 1, 1), ws.Cells(r + TRIM_SZ - 1, 1)).EntireRow.Hidden = True
Next
Set newWs = Worksheets.Add(After:=Worksheets(Worksheets.Count))
newWs.Name = Left("Trimmed " & ws.Name, 30)
ws.UsedRange.SpecialCells(xlCellTypeVisible).Copy newWs.Cells(1)
ws.Delete
Next
FastWB False: Debug.Print "Time: " & Format(Timer - t, "0.000") & " sec"
End Sub
Module 2 - setup test data subs, and helper procedures
Option Explicit
'generates 30 million formulas (3 full columns) on 10 Worksheets, in about 1 min
Public Sub MakeTestData()
Dim t As Double, ur As Range, ws As Worksheet
t = Timer
FastWB True
FormatCells
MakeWorksheets
With ThisWorkbook
Set ws = .Worksheets(1)
Set ur = ws.Range("A1:C" & ws.Rows.Count)
ur.Formula = "=Address(Row(), Column(), 4)"
.Worksheets.FillAcrossSheets ur
End With
FastWB False
Debug.Print "Time: " & Format(Timer - t, "0.000") & " sec"
End Sub
Private Sub FormatCells()
With ThisWorkbook.Worksheets(1).Cells
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = False
.IndentLevel = 0
.MergeCells = False
End With
End Sub
Private Sub MakeWorksheets()
Dim ws As Worksheet, i As Long, wsName As Long
With ThisWorkbook
If .Worksheets.Count > 1 Then
For Each ws In .Worksheets
If ws.Index <> 1 Then ws.Delete
Next
End If
For i = 1 To 10
wsName = .Worksheets.Count
.Worksheets.Add(After:=.Worksheets(wsName)).Name = wsName
Next
End With
End Sub
Public Sub FastWB(Optional ByVal opt As Boolean = True)
With Application
.Calculation = IIf(opt, xlCalculationManual, xlCalculationAutomatic)
.DisplayAlerts = Not opt
.DisplayStatusBar = Not opt
.EnableAnimations = Not opt
.EnableEvents = Not opt
.ScreenUpdating = Not opt
End With
FastWS , opt
End Sub
Public Sub FastWS(Optional ByVal ws As Worksheet = Nothing, _
Optional ByVal opt As Boolean = True)
If ws Is Nothing Then
For Each ws In Application.ActiveWorkbook.Sheets
EnableWS ws, opt
Next
Else
EnableWS ws, opt
End If
End Sub
Private Sub EnableWS(ByVal ws As Worksheet, ByVal opt As Boolean)
With ws
.DisplayPageBreaks = False
.EnableCalculation = Not opt
.EnableFormatConditionsCalculation = Not opt
.EnablePivotTable = Not opt
End With
End Sub
I think your biggest performance anchor is that you're deleting so frequently and Excel is having to move around so much data. You might consider clearing contents first and/or using a UNION function to do the delete all in one effort. So here's an example of how both approaches would be written:
Sub UnionExample()
Dim deleteRNG As Range
'You need one start statement that is not a union.
Set deleteRNG = Rows(2)
'Now you can start a loop or use some method to include members in your delete range
Set deleteRNG = Union(deleteRNG, Rows(4))
'when finished creating the delete range, clear contents (it's helped my performance)
deleteRNG.ClearContents
'then do your full delete
deleteRNG.Delete shift:=xlUp
End Sub
Using The SpreadSheetGuru's Timer I removed a total of 1,599,992 from 4 Worksheets in 13.53 seconds.
Sub ProcessWorksheets()
Dim ws As Worksheet
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
For Each ws In ThisWorkbook.Worksheets
KeepNthRows ws.UsedRange, 2, 1000
Next
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Sub KeepNthRows(Target As Range, FirstRow As Long, NthStep As Long)
Dim data As Variant, results As Variant
Dim x1 As Long, x2 As Long, y As Long
If Target.Rows.Count < 2 Then Exit Sub
FirstRow = FirstRow - 1 'Adjustment needed for using Range.Offset
data = Target.Offset(FirstRow).Value
ReDim results(1 To UBound(data, 1), 1 To UBound(data, 2))
For x1 = FirstRow To UBound(data, 1) Step NthStep
x2 = x2 + 1
For y = 1 To UBound(data, 2)
results(x2, y) = data(x1, y)
Next
Next
Target.Offset(FirstRow).Value = results
End Sub

type mismatch VBA checking cell value

I am trying to understand why I get Type mismatch error:
This is the function I have, basically it is copying from a worksheet to another and afterwards deleting the first character of the copied cells:
Sub copyBackFormulas()
Application.ScreenUpdating = False
Application.EnableEvents = False
'iterate through all worksheets
Dim WS_Count As Integer
WS_Count = ActiveWorkbook.Worksheets.Count
Dim I As Integer
For I = 1 To WS_Count
Dim ws1 As Worksheet
Set ws1 = ThisWorkbook.Worksheets(I)
'if sheet contains evdre
Set d = ws1.Cells.Find("EVDRE:OK")
If Not d Is Nothing Then
'copy back all formulas except from current view
Dim wsTarget As Worksheet
Set wsTarget = ws1
nameHidden = ActiveSheet.Name & "_BPCOffline"
Sheets(nameHidden).Visible = True
Dim wsSource As Worksheet
Set wsSource = Sheets(nameHidden)
For Each c In wsSource.UsedRange.Cells
If Left(c.Value, 1) = "_" Then
If Left(c.Value, 7) = "_=EVCVW" Then
Else
c.Copy wsTarget.Range(c.Address)
End If
End If
Next
'Remove underscore
For Each c In wsTarget.UsedRange.Cells
If Left(c.Value, 1) = "_" Then
c.Formula = Right(c.Value, Len(c.Value) - 1)
End If
Next
wsSource.Visible = xlSheetHidden
End If
Range("A1").Select
Next I
Application.CutCopyMode = False
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
I have several sheets that may need to be copied. The point is that I get type mismatch error on the line: If Left(c.Value, 1) = "_" Then
However, if I run the macro starting from other sheet it just works perfectly or it is only doing the right operations on one of the sheets and not the others.
I don't understand what makes it work at some point and what not.
Any input is highly appreciated
EDIT: I think the issue has to do with the fact that the macro may not find the first condition If Left(c.Value, 1) = "_" Then
You CAN'T copy paste formulas which have an error value
If you want to skip cells with errors you need another If...End if block:
If Not Iserror(c.Value) Then
...
End if
As explained by Rory in the comments